#include "MainWindow.h"

#include <QDesktopServices>
#include <QFileDialog>
#include <QIcon>
#include <QMessageBox>
#include <QMimeData>
#include <QUrl>

#include "../../../a64dbg/a64dbg.h"
#include "../../../a64dbg/HyperTraceView.h"
#include "AboutDialog.h"
#include "AppearanceDialog.h"
#include "AttachDialog.h"
#include "BreakpointsView.h"
#include "CPUArgumentWidget.h"
#include "CPUDisassembly.h"
#include "CPUMultiDump.h"
#include "CPUStack.h"
#include "CPUWidget.h"
#include "CalculatorDialog.h"
#include "CallStackView.h"
#include "CloseDialog.h"
#include "CommandLineEdit.h"
#include "Configuration.h"
#include "CustomizeMenuDialog.h"
#include "DebugStatusLabel.h"
#include "DisassemblerGraphView.h"
#include "FavouriteTools.h"
#include "GotoDialog.h"
#include "HandlesView.h"
#include "LineEditDialog.h"
#include "LogStatusLabel.h"
#include "LogView.h"
#include "MRUList.h"
#include "MainWindowCloseThread.h"
#include "MemoryMapView.h"
#include "MiscUtil.h"
#include "NotesManager.h"
#include "PatchDialog.h"
#include "ReferenceManager.h"
#include "SEHChainView.h"
#include "ScriptView.h"
#include "SettingsDialog.h"
#include "ShortcutsDialog.h"
#include "SimpleTraceDialog.h"
#include "SourceViewerManager.h"
#include "StringUtil.h"
#include "SymbolView.h"
#include "SystemBreakpointScriptDialog.h"
#include "TabWidget.h"
#include "ThreadView.h"
#include "TimeWastedCounter.h"
#include "Tracer/TraceBrowser.h"
#include "Tracer/TraceWidget.h"
#include "UpdateChecker.h"
#include "main.h"
#include "ui_MainWindow.h"

MainWindow *mainWindow = nullptr;

MainWindow::MainWindow(QWidget* parent)
    : QMainWindow(parent), ui(new Ui::MainWindow) {
  ui->setupUi(this);
  mainWindow = this;
#if 0
  // Build information
  {
    const char* debugEngine = [] {
      switch (DbgGetDebugEngine()) {
        case DebugEngineTitanEngine:
          return "TitanEngine";
        case DebugEngineGleeBug:
          return "GleeBug";
        case DebugEngineStaticEngine:
          return "StaticEngine";
      }
      return "";
    }();

    QAction* buildInfo = new QAction(
        tr("%1 (%2)").arg(ToDateString(GetCompileDate())).arg(debugEngine),
        this);
    buildInfo->setEnabled(false);
    ui->menuBar->addAction(buildInfo);
  }
#endif
  // Setup bridge signals
  connect(Bridge::getBridge(), SIGNAL(updateWindowTitle(QString)), this,
          SLOT(updateWindowTitleSlot(QString)));
  connect(Bridge::getBridge(), SIGNAL(addRecentFile(QString)), this,
          SLOT(addRecentFile(QString)));
  connect(Bridge::getBridge(), SIGNAL(setLastException(uint)), this,
          SLOT(setLastException(uint)));
  connect(Bridge::getBridge(),
          SIGNAL(menuAddMenuToList(QWidget*, QMenu*, GUIMENUTYPE, int)), this,
          SLOT(addMenuToList(QWidget*, QMenu*, GUIMENUTYPE, int)));
  connect(Bridge::getBridge(), SIGNAL(menuAddMenu(int, QString)), this,
          SLOT(addMenu(int, QString)));
  connect(Bridge::getBridge(), SIGNAL(menuAddMenuEntry(int, QString)), this,
          SLOT(addMenuEntry(int, QString)));
  connect(Bridge::getBridge(), SIGNAL(menuAddSeparator(int)), this,
          SLOT(addSeparator(int)));
  connect(Bridge::getBridge(), SIGNAL(menuClearMenu(int, bool)), this,
          SLOT(clearMenu(int, bool)));
  connect(Bridge::getBridge(), SIGNAL(menuRemoveMenuEntry(int)), this,
          SLOT(removeMenuEntry(int)));
  connect(Bridge::getBridge(), SIGNAL(getStrWindow(QString, QString*)), this,
          SLOT(getStrWindow(QString, QString*)));
  connect(Bridge::getBridge(), SIGNAL(setIconMenu(int, QIcon)), this,
          SLOT(setIconMenu(int, QIcon)));
  connect(Bridge::getBridge(), SIGNAL(setIconMenuEntry(int, QIcon)), this,
          SLOT(setIconMenuEntry(int, QIcon)));
  connect(Bridge::getBridge(), SIGNAL(setCheckedMenuEntry(int, bool)), this,
          SLOT(setCheckedMenuEntry(int, bool)));
  connect(Bridge::getBridge(),
          SIGNAL(setHotkeyMenuEntry(int, QString, QString)), this,
          SLOT(setHotkeyMenuEntry(int, QString, QString)));
  connect(Bridge::getBridge(), SIGNAL(setVisibleMenuEntry(int, bool)), this,
          SLOT(setVisibleMenuEntry(int, bool)));
  connect(Bridge::getBridge(), SIGNAL(setVisibleMenu(int, bool)), this,
          SLOT(setVisibleMenu(int, bool)));
  connect(Bridge::getBridge(), SIGNAL(setNameMenuEntry(int, QString)), this,
          SLOT(setNameMenuEntry(int, QString)));
  connect(Bridge::getBridge(), SIGNAL(setNameMenu(int, QString)), this,
          SLOT(setNameMenu(int, QString)));
  connect(Bridge::getBridge(), SIGNAL(showCpu()), this,
          SLOT(displayCpuWidget()));
  connect(Bridge::getBridge(), SIGNAL(showReferences()), this,
          SLOT(displayReferencesWidget()));
  connect(Bridge::getBridge(), SIGNAL(addQWidgetTab(QWidget*)), this,
          SLOT(addQWidgetTab(QWidget*)));
  connect(Bridge::getBridge(), SIGNAL(showQWidgetTab(QWidget*)), this,
          SLOT(showQWidgetTab(QWidget*)));
  connect(Bridge::getBridge(), SIGNAL(closeQWidgetTab(QWidget*)), this,
          SLOT(closeQWidgetTab(QWidget*)));
  connect(Bridge::getBridge(), SIGNAL(executeOnGuiThread(void*, void*)), this,
          SLOT(executeOnGuiThread(void*, void*)));
  connect(Bridge::getBridge(), SIGNAL(dbgStateChanged(DBGSTATE)), this,
          SLOT(dbgStateChangedSlot(DBGSTATE)));
  connect(Bridge::getBridge(), SIGNAL(addFavouriteItem(int, QString, QString)),
          this, SLOT(addFavouriteItem(int, QString, QString)));
  connect(Bridge::getBridge(),
          SIGNAL(setFavouriteItemShortcut(int, QString, QString)), this,
          SLOT(setFavouriteItemShortcut(int, QString, QString)));
  connect(Bridge::getBridge(), SIGNAL(selectInMemoryMap(duint)), this,
          SLOT(displayMemMapWidget()));
  connect(Bridge::getBridge(), SIGNAL(closeApplication()), this, SLOT(close()));
  connect(Bridge::getBridge(), SIGNAL(focusDisasm()), this,
          SLOT(displayCpuWidgetShowCpu()));
  connect(Bridge::getBridge(), SIGNAL(focusLog()), this,
          SLOT(displayLogWidget()));
  connect(Bridge::getBridge(), SIGNAL(focusSymbol()), this,
          SLOT(displaySymbolWidget()));
  connect(Bridge::getBridge(), SIGNAL(focusDecompiler()), this,
          SLOT(displaySourceViewWidget()));

  // Setup menu API
  initMenuApi();
  Bridge::getBridge()->emitMenuAddToList(this, ui->menuPlugins,
                                         GUI_PLUGIN_MENU);

  // Set window title
  if (BridgeIsProcessElevated()) {
    mWindowMainTitle =
        tr("%1 [Elevated]").arg(QCoreApplication::applicationName());
    ui->actionRestartAdmin->setEnabled(false);
  } else {
    mWindowMainTitle = tr("%1 v%2" A64DBG_BUILD_INFO)
                           .arg(QCoreApplication::applicationName())
                           .arg(A64DBG_VERSION)
#if !A64DBG_RELEASE
                           .arg(ToDateString(GetCompileDate()))
#endif
        ;
  }
  setWindowTitle(QString(mWindowMainTitle));

  // Load application icon
  SetApplicationIcon(MainWindow::winId());

  // Load recent files
  mMRUList = new MRUList(this, "Recent Files");
  connect(mMRUList, SIGNAL(openFile(QString)), this,
          SLOT(openRecentFileSlot(QString)));
  mMRUList->load();
  updateMRUMenu();

  // Accept drops
  setAcceptDrops(true);

  // Log view
  mLogView = new LogView();
  mLogView->setWindowTitle(tr("Log"));
  mLogView->setWindowIcon(DIcon("log.png"));
  mLogView->hide();

  // Symbol view
  mSymbolView = new SymbolView();
  Bridge::getBridge()->symbolView = mSymbolView;
  mSymbolView->setWindowTitle(tr("Symbols"));
  mSymbolView->setWindowIcon(DIcon("pdb.png"));
  mSymbolView->hide();

  // Decompiler Source view
  mSourceViewManager = new SourceViewerManager();
  mSourceViewManager->setWindowTitle(tr("UrDecompiler"));
  mSourceViewManager->setWindowIcon(DIcon("compile.png"));
  mSourceViewManager->hide();

  // Breakpoints
  mBreakpointsView = new BreakpointsView();
  mBreakpointsView->setWindowTitle(tr("Breakpoints"));
  mBreakpointsView->setWindowIcon(DIcon("breakpoint.png"));
  mBreakpointsView->hide();

  // Memory map view
  mMemMapView = nullptr;  // new MemoryMapView();
#if 0
    connect(mMemMapView, SIGNAL(showReferences()), this, SLOT(displayReferencesWidget()));
    mMemMapView->setWindowTitle(tr("Memory Map"));
    mMemMapView->setWindowIcon(DIcon("memory-map.png"));
    mMemMapView->hide();
#endif

  // Callstack view
  mCallStackView = new CallStackView();
  mCallStackView->setWindowTitle(tr("Call Stack"));
  mCallStackView->setWindowIcon(DIcon("callstack.png"));

  // SEH Chain view
  mSEHChainView = nullptr;  // new SEHChainView();
#if 0
    mSEHChainView->setWindowTitle(tr("SEH"));
    mSEHChainView->setWindowIcon(DIcon("seh-chain.png"));
#endif

  // Script view
  mScriptView = nullptr;  // new ScriptView();
#if 0
    mScriptView->setWindowTitle(tr("Script"));
    mScriptView->setWindowIcon(DIcon("script-code.png"));
    mScriptView->hide();
#endif

  // CPU view
  mCpuWidget = new CPUWidget(this);
  mCpuWidget->setWindowTitle(tr("CPU"));
#ifdef _WIN64
  mCpuWidget->setWindowIcon(DIcon("processor-cpu.png"));
#else
  mCpuWidget->setWindowIcon(DIcon("processor32.png"));
  ui->actionCpu->setIcon(DIcon("processor32.png"));
#endif  //_WIN64

  // Reference manager
  mReferenceManager = new ReferenceManager(this);
  Bridge::getBridge()->referenceManager = mReferenceManager;
  mReferenceManager->setWindowTitle(tr("References"));
  mReferenceManager->setWindowIcon(DIcon("search.png"));

  // Thread view
  mThreadView = new ThreadView();
  mThreadView->setWindowTitle(tr("Threads"));
  mThreadView->setWindowIcon(DIcon("arrow-threads.png"));

  // Notes manager
  mNotesManager = new NotesManager(this);
  mNotesManager->setWindowTitle(tr("Notes"));
  mNotesManager->setWindowIcon(DIcon("notes.png"));

  // Handles view
  mHandlesView = nullptr;  // new HandlesView(this);
#if 0
    mHandlesView->setWindowTitle(tr("Handles"));
    mHandlesView->setWindowIcon(DIcon("handles.png"));
#endif

  // Trace view
  mTraceWidget = new TraceWidget(this);
  mTraceWidget->setWindowTitle(tr("Trace"));
  mTraceWidget->setWindowIcon(DIcon("trace.png"));
  connect(mTraceWidget->getTraceBrowser(), SIGNAL(displayReferencesWidget()),
          this, SLOT(displayReferencesWidget()));
  connect(mTraceWidget->getTraceBrowser(), SIGNAL(displayLogWidget()), this,
          SLOT(displayLogWidget()));

  mHyperTraceWidget = new HyperTraceView();
  mHyperTraceWidget->setWindowTitle(tr("HyperTrace"));
  mHyperTraceWidget->setWindowIcon(DIcon("follow_in_dump.png"));
  mHyperTraceWidget->hide();

  mTabWidget = new MHTabWidget(this, true, true);

  // Add all widgets to the list
  mWidgetList.push_back(WidgetInfo(mCpuWidget, "CPUTab"));
  mWidgetList.push_back(WidgetInfo(mCallStackView, "CallStackTab"));
  mWidgetList.push_back(WidgetInfo(mThreadView, "ThreadsTab"));
  mWidgetList.push_back(WidgetInfo(mSymbolView, "SymbolsTab"));
  mWidgetList.push_back(WidgetInfo(mBreakpointsView, "BreakpointsTab"));
  mWidgetList.push_back(WidgetInfo(mLogView, "LogTab"));
  mWidgetList.push_back(WidgetInfo(mTraceWidget, "TraceTab"));
  mWidgetList.push_back(WidgetInfo(mHyperTraceWidget, "HyperTraceTab"));
  mWidgetList.push_back(WidgetInfo(mSourceViewManager, "SourceTab"));
  mWidgetList.push_back(WidgetInfo(mReferenceManager, "ReferencesTab"));
  mWidgetList.push_back(WidgetInfo(mNotesManager, "NotesTab"));
  // mWidgetList.push_back(WidgetInfo(mMemMapView, "MemoryMapTab"));
  // mWidgetList.push_back(WidgetInfo(mSEHChainView, "SEHTab"));
  // mWidgetList.push_back(WidgetInfo(mScriptView, "ScriptTab"));
  // mWidgetList.push_back(WidgetInfo(mHandlesView, "HandlesTab"));

  // always use a64dbg's tab order
  if (1 || !ConfigBool("Gui", "LoadSaveTabOrder"))
    loadTabDefaultOrder();
  else
    loadTabSavedOrder();

  setCentralWidget(mTabWidget);

  // Setup the command and status bars
  setupCommandBar();
  setupStatusBar();

  // Patch dialog
  mPatchDialog = new PatchDialog(this);
  mCalculatorDialog = new CalculatorDialog(this);

  // Setup signals/slots
  connect(mCmdLineEdit, SIGNAL(returnPressed()), this, SLOT(executeCommand()));
  makeCommandAction(ui->actionStepOver, "StepOver");
  makeCommandAction(ui->actionStepInto, "StepInto");
  connect(ui->actionCommand, SIGNAL(triggered()), this,
          SLOT(setFocusToCommandBar()));
  makeCommandAction(ui->actionClose, "stop");
  connect(ui->actionMemoryMap, SIGNAL(triggered()), this,
          SLOT(displayMemMapWidget()));
  connect(ui->actionRun, SIGNAL(triggered()), this, SLOT(runSlot()));
  makeCommandAction(ui->actionRtr, "rtr");
  makeCommandAction(ui->actionExecute_till_bbend, "rtbbe");
  connect(ui->actionLog, SIGNAL(triggered()), this, SLOT(displayLogWidget()));
  connect(ui->actionAbout, SIGNAL(triggered()), this,
          SLOT(displayAboutWidget()));
  connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(openFileSlot()));
  connect(ui->actionPython_script, SIGNAL(triggered()), this, SLOT(openPythonFileSlot()));
  connect(ui->actionADCpp_script, SIGNAL(triggered()), this, SLOT(openADCppFileSlot()));
  connect(ui->actionADCpp_module, SIGNAL(triggered()), this, SLOT(openADCppModuleSlot()));
  connect(ui->actionCompile_adcpp, SIGNAL(triggered()), this, SLOT(openCompileADCppSlot()));
  connect(ui->actionLaunch_iOS_App, SIGNAL(triggered()), this,
          SLOT(launchiOSAppSlot()));
  connect(ui->actionLaunch_APK_Activity, SIGNAL(triggered()), this,
          SLOT(launchApkAppSlot()));
  connect(ui->actionPause, SIGNAL(triggered()), this, SLOT(pauseSlot()));
  makeCommandAction(ui->actionScylla, "StartScylla");
  connect(ui->actionRestart, SIGNAL(triggered()), this,
          SLOT(restartDebugging()));
  connect(ui->actionBreakpoints, SIGNAL(triggered()), this,
          SLOT(displayBreakpointWidget()));
  makeCommandAction(ui->actioneStepOver, "eStepOver");
  makeCommandAction(ui->actioneStepInto, "eStepInto");
  makeCommandAction(ui->actioneRun, "eRun");
  makeCommandAction(ui->actioneRtr, "eRtr");
  makeCommandAction(ui->actionRtu, "TraceOverConditional mod.user(cip)");
  connect(ui->actionTicnd, SIGNAL(triggered()), this, SLOT(execTicnd()));
  connect(ui->actionTocnd, SIGNAL(triggered()), this, SLOT(execTocnd()));
  connect(ui->actionTRBit, SIGNAL(triggered()), this, SLOT(execTRBit()));
  connect(ui->actionTRByte, SIGNAL(triggered()), this, SLOT(execTRByte()));
  connect(ui->actionTRWord, SIGNAL(triggered()), this, SLOT(execTRWord()));
  connect(ui->actionTRNone, SIGNAL(triggered()), this, SLOT(execTRNone()));
  makeCommandAction(ui->actionTRTIBT, "tibt");
  makeCommandAction(ui->actionTRTOBT, "tobt");
  makeCommandAction(ui->actionTRTIIT, "tiit");
  makeCommandAction(ui->actionTRTOIT, "toit");
  makeCommandAction(ui->actionInstrUndo, "InstrUndo");
  makeCommandAction(ui->actionSkipNextInstruction, "skip");
  connect(ui->actionScript, SIGNAL(triggered()), this,
          SLOT(displayScriptWidget()));
  connect(ui->actionRunSelection, SIGNAL(triggered()), this,
          SLOT(runSelection()));
  connect(ui->actionRunExpression, SIGNAL(triggered(bool)), this,
          SLOT(runExpression()));
  makeCommandAction(ui->actionHideDebugger, "hide");
  connect(ui->actionCpu, SIGNAL(triggered()), this,
          SLOT(displayCpuWidgetShowCpu()));
  connect(ui->actionSymbolInfo, SIGNAL(triggered()), this,
          SLOT(displaySymbolWidget()));
  connect(ui->actionModules, SIGNAL(triggered()), this,
          SLOT(displaySymbolWidget()));
  connect(ui->actionSource, SIGNAL(triggered()), this,
          SLOT(displaySourceViewWidget()));
  // connect(mSymbolView, SIGNAL(showReferences()), this,
  // SLOT(displayReferencesWidget()));
  connect(ui->actionReferences, SIGNAL(triggered()), this,
          SLOT(displayReferencesWidget()));
  connect(ui->actionThreads, SIGNAL(triggered()), this,
          SLOT(displayThreadsWidget()));
  connect(ui->actionSettings, SIGNAL(triggered()), this, SLOT(openSettings()));
  connect(ui->actionStrings, SIGNAL(triggered()), this, SLOT(findStrings()));
  connect(ui->actionCalls, SIGNAL(triggered()), this, SLOT(findModularCalls()));
  connect(ui->actionAppearance, SIGNAL(triggered()), this,
          SLOT(openAppearance()));
  connect(ui->actionShortcuts, SIGNAL(triggered()), this,
          SLOT(openShortcuts()));
  connect(ui->actionTopmost, SIGNAL(toggled(bool)), this,
          SLOT(changeTopmost(bool)));
  connect(ui->actionCalculator, SIGNAL(triggered()), this,
          SLOT(openCalculator()));
  connect(ui->actionPatches_2, SIGNAL(triggered()), this,
          SLOT(displayPatches()));
  connect(ui->actionComments, SIGNAL(triggered()), this,
          SLOT(displayComments()));
  connect(ui->actionLabels, SIGNAL(triggered()), this, SLOT(displayLabels()));
  connect(ui->actionBookmarks, SIGNAL(triggered()), this,
          SLOT(displayBookmarks()));
  connect(ui->actionFunctions, SIGNAL(triggered()), this,
          SLOT(displayFunctions()));
  connect(ui->actionCallStack, SIGNAL(triggered()), this,
          SLOT(displayCallstack()));
  connect(ui->actionSEHChain, SIGNAL(triggered()), this,
          SLOT(displaySEHChain()));
  connect(ui->actionTrace, SIGNAL(triggered()), this, SLOT(displayRunTrace()));
  connect(ui->actionDonate, SIGNAL(triggered()), this, SLOT(donate()));
  connect(ui->actionReportBug, SIGNAL(triggered()), this, SLOT(reportBug()));
  connect(ui->actionBlog, SIGNAL(triggered()), this, SLOT(blog()));
  connect(ui->actionCrashDump, SIGNAL(triggered()), this, SLOT(crashDump()));
  connect(ui->actionAttach, SIGNAL(triggered()), this, SLOT(displayAttach()));
  makeCommandAction(ui->actionDetach, "detach");
  connect(ui->actionChangeCommandLine, SIGNAL(triggered()), this,
          SLOT(changeCommandLine()));
  connect(ui->actionManual, SIGNAL(triggered()), this, SLOT(displayManual()));
  connect(ui->actionNotes, SIGNAL(triggered()), this,
          SLOT(displayNotesWidget()));
  connect(ui->actionHandles, SIGNAL(triggered()), this,
          SLOT(displayHandlesWidget()));
  connect(ui->actionGraph, SIGNAL(triggered()), this,
          SLOT(displayGraphWidget()));
  connect(ui->actionPreviousTab, SIGNAL(triggered()), this,
          SLOT(displayPreviousTab()));
  connect(ui->actionNextTab, SIGNAL(triggered()), this, SLOT(displayNextTab()));
  connect(ui->actionPreviousView, SIGNAL(triggered()), this,
          SLOT(displayPreviousView()));
  connect(ui->actionNextView, SIGNAL(triggered()), this,
          SLOT(displayNextView()));
  connect(ui->actionHideTab, SIGNAL(triggered()), this, SLOT(hideTab()));
  connect(ui->actionADPManager, SIGNAL(triggered()), this,
          SLOT(managePlugin()));
  makeCommandAction(ui->actionStepIntoSource,
                    "TraceIntoConditional src.line(cip) && !src.disp(cip)");
  makeCommandAction(ui->actionStepOverSource,
                    "TraceOverConditional src.line(cip) && !src.disp(cip)");
  makeCommandAction(ui->actionseStepInto, "seStepInto");
  makeCommandAction(ui->actionseStepOver, "seStepOver");
  makeCommandAction(ui->actionseRun, "seRun");
  connect(ui->actionAnimateInto, SIGNAL(triggered()), this,
          SLOT(animateIntoSlot()));
  connect(ui->actionAnimateOver, SIGNAL(triggered()), this,
          SLOT(animateOverSlot()));
  connect(ui->actionAnimateCommand, SIGNAL(triggered()), this,
          SLOT(animateCommandSlot()));
  connect(ui->actionSetInitializationScript, SIGNAL(triggered()), this,
          SLOT(setInitializationScript()));
  connect(ui->actionCustomizeMenus, SIGNAL(triggered()), this,
          SLOT(customizeMenu()));
  connect(ui->actionVariables, SIGNAL(triggered()), this,
          SLOT(displayVariables()));

  connect(mCpuWidget->getDisasmWidget(), SIGNAL(updateWindowTitle(QString)),
          this, SLOT(updateWindowTitleSlot(QString)));
  connect(mCpuWidget->getDisasmWidget(), SIGNAL(displayReferencesWidget()),
          this, SLOT(displayReferencesWidget()));
  connect(mCpuWidget->getDisasmWidget(), SIGNAL(displaySourceManagerWidget()),
          this, SLOT(displaySourceViewWidget()));
  connect(mCpuWidget->getDisasmWidget(), SIGNAL(displayLogWidget()), this,
          SLOT(displayLogWidget()));
  connect(mCpuWidget->getDisasmWidget(), SIGNAL(displaySymbolsWidget()), this,
          SLOT(displaySymbolWidget()));
  connect(mCpuWidget->getDisasmWidget(), SIGNAL(showPatches()), this,
          SLOT(displayPatches()));
  connect(mCpuWidget->getGraphWidget(), SIGNAL(displayLogWidget()), this,
          SLOT(displayLogWidget()));

  connect(mCpuWidget->getDumpWidget(), SIGNAL(displayReferencesWidget()), this,
          SLOT(displayReferencesWidget()));

  connect(mCpuWidget->getStackWidget(), SIGNAL(displayReferencesWidget()), this,
          SLOT(displayReferencesWidget()));

  connect(mTabWidget, SIGNAL(tabMovedTabWidget(int, int)), this,
          SLOT(tabMovedSlot(int, int)));
  connect(Config(), SIGNAL(shortcutsUpdated()), this, SLOT(refreshShortcuts()));

  // Menu stuff
  actionManageFavourites = nullptr;
  mFavouriteToolbar = new QToolBar(tr("Favourite Toolbox"), this);
  updateFavouriteTools();
  setupLanguagesMenu();
  setupThemesMenu();
  setupMenuCustomization();

  lastException = 0;
  // Don't need to set shortcuts because the code above will signal
  // refreshShortcuts()

  // Update checker
  mUpdateChecker = new UpdateChecker(this);

  // Setup close thread and dialog
  bCanClose = false;
  mCloseThread = new MainWindowCloseThread(this);
  connect(mCloseThread, SIGNAL(canClose()), this, SLOT(canClose()));
  mCloseDialog = new CloseDialog(this);

  mCpuWidget->setDisasmFocus();
  Config()->readShortcuts();
  setGlobalShortcut(ui->actionExecute_till_bbend, QKeySequence("Ctrl+F4"));

  QTimer::singleShot(0, this, SLOT(loadWindowSettings()));

  addPluginMenu();
}

MainWindow::~MainWindow() {
  if (mADPManager) {
    mADPManager->close();
    delete mADPManager;
  }
  delete ui;
}

void MainWindow::setupCommandBar() {
  mCmdLineEdit = new CommandLineEdit(ui->cmdBar);
  ui->cmdBar->addWidget(new QLabel(tr("Command: ")));
  ui->cmdBar->addWidget(mCmdLineEdit);
  ui->cmdBar->addWidget(mCmdLineEdit->selectorWidget());
}

void MainWindow::setupStatusBar() {
  // Status label (Ready, Paused, ...)
  mStatusLabel = new DebugStatusLabel(ui->statusBar);
  mStatusLabel->setText(tr("Ready"));
  ui->statusBar->addWidget(mStatusLabel);

  // Log line
  mLastLogLabel = new LogStatusLabel(ui->statusBar);
  ui->statusBar->addPermanentWidget(mLastLogLabel, 1);

  // Time wasted counter
  QLabel* timeWastedLabel = new QLabel(this);
  ui->statusBar->addPermanentWidget(timeWastedLabel);
  mTimeWastedCounter = new TimeWastedCounter(this, timeWastedLabel);
}

void MainWindow::setupLanguagesMenu() {
  QMenu* languageMenu;
  if (tr("Languages") == QString("Languages"))
    languageMenu = new QMenu(QString("Languages"));
  else
    languageMenu = new QMenu(tr("Languages") + QString(" Languages"), this);
  languageMenu->setIcon(DIcon("codepage.png"));

  QLocale enUS(QLocale::English, QLocale::UnitedStates);
  QAction* action_enUS = new QAction(QString("[%1] %2 - %3")
                                         .arg(enUS.name())
                                         .arg(enUS.nativeLanguageName())
                                         .arg(enUS.nativeCountryName()),
                                     languageMenu);
  connect(action_enUS, SIGNAL(triggered()), this, SLOT(chooseLanguage()));
  action_enUS->setCheckable(true);
  action_enUS->setChecked(false);
  languageMenu->addAction(action_enUS);
  connect(languageMenu, SIGNAL(aboutToShow()), this,
          SLOT(setupLanguagesMenu2()));  // Load this menu later, since it
                                         // requires directory scanning.
  ui->menuOptions->addMenu(languageMenu);
}

#include "../bridge/Utf8Ini.h"

static void importSettings(const QString& filename,
                           const QSet<QString>& sectionWhitelist = {}) {
  QFile f(QDir::toNativeSeparators(filename));
  if (f.open(QFile::ReadOnly | QFile::Text)) {
    QTextStream in(&f);
    auto style = in.readAll();
    f.close();
    Utf8Ini ini;
    int errorLine;
    if (ini.Deserialize(style.toStdString(), errorLine)) {
      auto sections = ini.Sections();
      for (const auto& section : sections) {
        if (!sectionWhitelist.isEmpty() &&
            !sectionWhitelist.contains(QString::fromStdString(section)))
          continue;

        auto keys = ini.Keys(section);
        for (const auto& key : keys)
          BridgeSettingSet(section.c_str(), key.c_str(),
                           ini.GetValue(section, key).c_str());
      }
      Config()->load();
      DbgSettingsUpdated();
      emit Config()->colorsUpdated();
      emit Config()->fontsUpdated();
      emit Config()->guiOptionsUpdated();
      emit Config()->shortcutsUpdated();
      emit Config()->tokenizerConfigUpdated();
      GuiUpdateAllViews();
    }
  }
}

void MainWindow::loadSelectedStyle(bool reloadStyleCss) {
  char selectedTheme[MAX_SETTING_SIZE] = "";
  QString stylePath(":/css/default.css");
  QString styleSettings;
  if (BridgeSettingGet("Theme", "Selected", selectedTheme) && *selectedTheme) {
    QString themePath = QString("%1/../themes/%2/style.css")
                            .arg(QCoreApplication::applicationDirPath())
                            .arg(selectedTheme);
    if (QFile(themePath).exists()) stylePath = themePath;
    QString settingsPath = QString("%1/../themes/%2/style.ini")
                               .arg(QCoreApplication::applicationDirPath())
                               .arg(selectedTheme);
    if (QFile(themePath).exists()) styleSettings = settingsPath;
  }
  QFile f(stylePath);
  if (f.open(QFile::ReadOnly | QFile::Text)) {
    QTextStream in(&f);
    auto style = in.readAll();
    f.close();
    style = style.replace("url(./",
                          QString("url(../themes/%2/").arg(selectedTheme));
    qApp->setStyleSheet(style);
  }
  if (!reloadStyleCss && !styleSettings.isEmpty())
    importSettings(styleSettings, {"Colors", "Fonts"});
}

void MainWindow::managePlugin() {
  if (!mADPManager) {
    mADPManager = new ADPluginView;
  }
  mADPManager->show();
}

void MainWindow::themeTriggeredSlot() {
  QAction* action = qobject_cast<QAction*>(sender());
  if (action == nullptr) return;
  QString dir = action->data().toString();
  int nameIdx = dir.lastIndexOf('/');
  QString name = dir.mid(nameIdx + 1);
  BridgeSettingSet("Theme", "Selected", name.toUtf8().constData());
  loadSelectedStyle();
}

void MainWindow::setupThemesMenu() {
  auto exists = [](const QString& str) { return QFile(str).exists(); };
  QDirIterator it(
      QString("%1/../themes").arg(QCoreApplication::applicationDirPath()),
      QDir::NoDotAndDotDot | QDir::Dirs);
  while (it.hasNext()) {
    auto dir = it.next();
    auto nameIdx = dir.lastIndexOf('/');
    auto name = dir.mid(nameIdx + 1);
    auto action = ui->menuTheme->addAction(name);
    connect(action, SIGNAL(triggered()), this, SLOT(themeTriggeredSlot()));
    action->setText(name);
    action->setData(dir);
  }
}

void MainWindow::setupLanguagesMenu2() {
  QMenu* languageMenu =
      dynamic_cast<QMenu*>(sender());  // The only sender is languageMenu
  QAction* action_enUS =
      languageMenu->actions()[0];  // There is only one action "action_enUS"
                                   // created by setupLanguagesMenu()
  QDir translationsDir(QString("%1/../translations/")
                           .arg(QCoreApplication::applicationDirPath()));
  QString wCurrentLocale(currentLocale);

  if (!translationsDir.exists()) {
    // translations dir do not exist
    action_enUS->setChecked(true);
    disconnect(languageMenu, SIGNAL(aboutToShow()), this, 0);
    return;
  }
  if (wCurrentLocale == QString("en_US")) action_enUS->setChecked(true);
  QStringList filter;
  filter << "x64dbg_*.qm";
  QFileInfoList fileList =
      translationsDir.entryInfoList(filter, QDir::Readable | QDir::Files,
                                    QDir::Size);  // Search for all translations
  auto allLocales = QLocale::matchingLocales(
      QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
  for (auto i : fileList) {
    QString localeName = i.baseName().mid(7);
    for (auto j : allLocales) {
      if (j.name().startsWith(localeName)) {
        QAction* actionLanguage = new QAction(QString("[%1] %2 - %3")
                                                  .arg(localeName)
                                                  .arg(j.nativeLanguageName())
                                                  .arg(j.nativeCountryName()),
                                              languageMenu);
        connect(actionLanguage, SIGNAL(triggered()), this,
                SLOT(chooseLanguage()));
        actionLanguage->setCheckable(true);
        actionLanguage->setChecked(localeName == wCurrentLocale);
        languageMenu->addAction(actionLanguage);
        break;
      }
    }
  }
  disconnect(
      languageMenu, SIGNAL(aboutToShow()), this,
      0);  // Done. Let's disconnect it to prevent it from initializing twice.
}

void MainWindow::closeEvent(QCloseEvent* event) {
  if (!bCanClose && DbgIsDebugging() && ConfigBool("Gui", "ShowExitConfirmation")) {
    auto cb = new QCheckBox(tr("Don't ask this question again"));
    QMessageBox msgbox(this);
    msgbox.setText(
        tr("The debuggee is still running and will be terminated if you exit. "
           "Do you really want to exit?"));
    msgbox.setWindowTitle(tr("Debuggee is still running"));
    msgbox.setWindowIcon(DIcon("bug.png"));
    msgbox.addButton(QMessageBox::Yes);
    msgbox.addButton(QMessageBox::No);
    msgbox.setDefaultButton(QMessageBox::No);
    msgbox.setCheckBox(cb);

    QObject::connect(cb, &QCheckBox::toggled, [](bool checked) {
      Config()->setBool("Gui", "ShowExitConfirmation", !checked);
    });

    if (msgbox.exec() != QMessageBox::Yes) {
      event->ignore();
      return;
    }
  }

  duint noClose = 0;
  if (bCanClose) {
    saveWindowSettings();
  }
  else if (DbgIsDebugging()) {
    DbgCmdExec("stop");
  }
  if (BridgeSettingGetUint("Gui", "NoCloseDialog", &noClose) && noClose)
    mCloseDialog->hide();
  else {
    mCloseDialog->show();
    mCloseDialog->setFocus();
  }
  static volatile bool bExecuteThread = true;
  if (bExecuteThread) {
    bExecuteThread = false;
    QThread::msleep(100);
    mCloseThread->start();
    emit Bridge::getBridge()->close();
  }
  if (bCanClose) {
    mCloseDialog->allowClose();
    mCloseDialog->close();
    event->accept();
  } else
    event->ignore();
}

void MainWindow::setTab(QWidget* widget) {
  // shown tabs
  for (int i = 0; i < mTabWidget->count(); i++) {
    if (mTabWidget->widget(i) == widget) {
      mTabWidget->setCurrentIndex(i);
      return;
    }
  }

  // hidden tabs
  for (int i = 0; i < mWidgetList.count(); i++) {
    if (mWidgetList[i].widget == widget) {
      addQWidgetTab(mWidgetList[i].widget, mWidgetList[i].nativeName);
      mTabWidget->setCurrentIndex(mTabWidget->count() - 1);
      return;
    }
  }

  // TODO: restore configuration index
}

void MainWindow::loadTabDefaultOrder() {
  clearTabWidget();

  // Setup tabs
  // TODO
  for (int i = 0; i < mWidgetList.size(); i++)
    addQWidgetTab(mWidgetList[i].widget, mWidgetList[i].nativeName);

  // Add plugin tabs to the end
  for (const auto& widget : mPluginWidgetList)
    addQWidgetTab(widget.widget, widget.nativeName);
}

void MainWindow::loadTabSavedOrder() {
  clearTabWidget();

  QMap<duint, std::pair<QWidget*, QString>> tabIndexToWidget;

  // Get tabIndex for each widget and add them to tabIndexToWidget
  for (int i = 0; i < mWidgetList.size(); i++) {
    QString tabName = mWidgetList[i].nativeName;
    duint tabIndex = Config()->getUint("TabOrder", tabName);
    if (!tabIndexToWidget.contains(tabIndex))
      tabIndexToWidget.insert(tabIndex,
                              std::make_pair(mWidgetList[i].widget, tabName));
    else {
      // Conflicts. Try to find an unused tab index.
      for (int j = 0; j < mWidgetList.size(); j++) {
        auto item = tabIndexToWidget.find(j);
        if (item == tabIndexToWidget.end()) {
          tabIndexToWidget.insert(
              j, std::make_pair(mWidgetList[i].widget, tabName));
          break;
        }
      }
    }
  }

  // Setup tabs
  for (auto& widget : tabIndexToWidget)
    addQWidgetTab(widget.first, widget.second);

  // 'Restore' deleted tabs
  for (int i = 0; i < mWidgetList.size(); i++) {
    duint isDeleted = 0;
    BridgeSettingGetUint("Deleted Tabs",
                         mWidgetList[i].nativeName.toUtf8().constData(),
                         &isDeleted);
    if (isDeleted)
      mTabWidget->DeleteTab(mTabWidget->indexOf(mWidgetList[i].widget));
  }

  // Add plugin tabs to the end
  for (const auto& widget : mPluginWidgetList)
    addQWidgetTab(widget.widget, widget.nativeName);
}

void MainWindow::clearTabWidget() {
  if (mTabWidget->count() <= 0) return;

  // Remove all tabs starting from the end
  for (int i = mTabWidget->count() - 1; i >= 0; i--) mTabWidget->removeTab(i);
}

void MainWindow::saveWindowSettings() {
  // Main Window settings
  BridgeSettingSet("Main Window Settings", "Geometry",
                   saveGeometry().toBase64().data());
  BridgeSettingSet("Main Window Settings", "State",
                   saveState().toBase64().data());

  // Set of currently detached tabs
  QSet<QWidget*> detachedTabWindows = mTabWidget->windows().toSet();

  // For all tabs, save detached status.  If detached, save geometry.
  for (int i = 0; i < mWidgetList.size(); i++) {
    bool isDetached = detachedTabWindows.contains(mWidgetList[i].widget);
    bool isDeleted =
        !isDetached && mTabWidget->indexOf(mWidgetList[i].widget) == -1;
    BridgeSettingSetUint("Detached Windows",
                         mWidgetList[i].nativeName.toUtf8().constData(),
                         isDetached);
    BridgeSettingSetUint("Deleted Tabs",
                         mWidgetList[i].nativeName.toUtf8().constData(),
                         isDeleted);
    if (isDetached)
      BridgeSettingSet("Tab Window Settings",
                       mWidgetList[i].nativeName.toUtf8().constData(),
                       mWidgetList[i]
                           .widget->parentWidget()
                           ->saveGeometry()
                           .toBase64()
                           .data());
  }

  // Save favourite toolbar
  BridgeSettingSetUint("Main Window Settings", "FavToolbarPositionX",
                       mFavouriteToolbar->x());
  BridgeSettingSetUint("Main Window Settings", "FavToolbarPositionY",
                       mFavouriteToolbar->y());
  BridgeSettingSetUint("Main Window Settings", "FavToolbarVisible",
                       mFavouriteToolbar->isVisible() ? 1 : 0);

  mCpuWidget->saveWindowSettings();
  mSymbolView->saveWindowSettings();
}

void MainWindow::loadWindowSettings() {
  // Main Window settings
  char setting[MAX_SETTING_SIZE] = "";
  if (BridgeSettingGet("Main Window Settings", "Geometry", setting))
    restoreGeometry(QByteArray::fromBase64(QByteArray(setting)));

  if (BridgeSettingGet("Main Window Settings", "State", setting))
    restoreState(QByteArray::fromBase64(QByteArray(setting)));

  // Restore detached windows size and position
  // If a tab was detached last session, manually detach it now to populate
  // MHTabWidget::windows
  for (int i = 0; i < mWidgetList.size(); i++) {
    duint isDetached = 0;
    BridgeSettingGetUint("Detached Windows",
                         mWidgetList[i].nativeName.toUtf8().constData(),
                         &isDetached);
    if (isDetached)
      mTabWidget->DetachTab(mTabWidget->indexOf(mWidgetList[i].widget),
                            QPoint());
  }

  // Restore geometry for every tab we just detached
  QSet<QWidget*> detachedTabWindows = mTabWidget->windows().toSet();
  for (int i = 0; i < mWidgetList.size(); i++) {
    if (detachedTabWindows.contains(mWidgetList[i].widget)) {
      if (BridgeSettingGet("Tab Window Settings",
                           mWidgetList[i].nativeName.toUtf8().constData(),
                           setting))
        mWidgetList[i].widget->parentWidget()->restoreGeometry(
            QByteArray::fromBase64(QByteArray(setting)));
    }
  }

  // 'Restore' deleted tabs
  for (int i = 0; i < mWidgetList.size(); i++) {
    duint isDeleted = 0;
    BridgeSettingGetUint("Deleted Tabs",
                         mWidgetList[i].nativeName.toUtf8().constData(),
                         &isDeleted);
    if (isDeleted)
      mTabWidget->DeleteTab(mTabWidget->indexOf(mWidgetList[i].widget));
  }

  // Load favourite toolbar
  duint posx = 0, posy = 0, isVisible = 0;
  BridgeSettingGetUint("Main Window Settings", "FavToolbarPositionX", &posx);
  BridgeSettingGetUint("Main Window Settings", "FavToolbarPositionY", &posy);
  BridgeSettingGetUint("Main Window Settings", "FavToolbarVisible", &isVisible);
  mFavouriteToolbar->move(posx, posy);
  mFavouriteToolbar->setVisible(isVisible == 1);

  mCpuWidget->loadWindowSettings();
  mSymbolView->loadWindowSettings();
}

void MainWindow::setGlobalShortcut(QAction* action, const QKeySequence& key) {
  action->setShortcut(key);
  action->setShortcutContext(Qt::ApplicationShortcut);
}

void MainWindow::refreshShortcuts() {
  setGlobalShortcut(ui->actionOpen, ConfigShortcut("FileOpen"));
  setGlobalShortcut(ui->actionAttach, ConfigShortcut("FileAttach"));
  setGlobalShortcut(ui->actionDetach, ConfigShortcut("FileDetach"));
  setGlobalShortcut(ui->actionImportdatabase,
                    ConfigShortcut("FileImportDatabase"));
  setGlobalShortcut(ui->actionExportdatabase,
                    ConfigShortcut("FileExportDatabase"));
  setGlobalShortcut(ui->actionRestartAdmin, ConfigShortcut("FileRestartAdmin"));
  setGlobalShortcut(ui->actionExit, ConfigShortcut("FileExit"));

  setGlobalShortcut(ui->actionCpu, ConfigShortcut("ViewCpu"));
  setGlobalShortcut(ui->actionLog, ConfigShortcut("ViewLog"));
  setGlobalShortcut(ui->actionBreakpoints, ConfigShortcut("ViewBreakpoints"));
  setGlobalShortcut(ui->actionMemoryMap, ConfigShortcut("ViewMemoryMap"));
  setGlobalShortcut(ui->actionCallStack, ConfigShortcut("ViewCallStack"));
  setGlobalShortcut(ui->actionSEHChain, ConfigShortcut("ViewSEHChain"));
  setGlobalShortcut(ui->actionScript, ConfigShortcut("ViewScript"));
  setGlobalShortcut(ui->actionSymbolInfo, ConfigShortcut("ViewSymbolInfo"));
  setGlobalShortcut(ui->actionModules, ConfigShortcut("ViewModules"));
  setGlobalShortcut(ui->actionSource, ConfigShortcut("ViewSource"));
  setGlobalShortcut(ui->actionReferences, ConfigShortcut("ViewReferences"));
  setGlobalShortcut(ui->actionThreads, ConfigShortcut("ViewThreads"));
  setGlobalShortcut(ui->actionPatches_2, ConfigShortcut("ViewPatches"));
  setGlobalShortcut(ui->actionComments, ConfigShortcut("ViewComments"));
  setGlobalShortcut(ui->actionLabels, ConfigShortcut("ViewLabels"));
  setGlobalShortcut(ui->actionBookmarks, ConfigShortcut("ViewBookmarks"));
  setGlobalShortcut(ui->actionFunctions, ConfigShortcut("ViewFunctions"));
  setGlobalShortcut(ui->actionVariables, ConfigShortcut("ViewVariables"));
  setGlobalShortcut(ui->actionHandles, ConfigShortcut("ViewHandles"));
  setGlobalShortcut(ui->actionGraph, ConfigShortcut("ViewGraph"));
  setGlobalShortcut(ui->actionPreviousTab, ConfigShortcut("ViewPreviousTab"));
  setGlobalShortcut(ui->actionNextTab, ConfigShortcut("ViewNextTab"));
  setGlobalShortcut(ui->actionPreviousView,
                    ConfigShortcut("ViewPreviousHistory"));
  setGlobalShortcut(ui->actionNextView, ConfigShortcut("ViewNextHistory"));
  setGlobalShortcut(ui->actionHideTab, ConfigShortcut("ViewHideTab"));

  setGlobalShortcut(ui->actionRun, ConfigShortcut("DebugRun"));
  setGlobalShortcut(ui->actioneRun, ConfigShortcut("DebugeRun"));
  setGlobalShortcut(ui->actionseRun, ConfigShortcut("DebugseRun"));
  setGlobalShortcut(ui->actionRunSelection,
                    ConfigShortcut("DebugRunSelection"));
  setGlobalShortcut(ui->actionRunExpression,
                    ConfigShortcut("DebugRunExpression"));
  setGlobalShortcut(ui->actionPause, ConfigShortcut("DebugPause"));
  setGlobalShortcut(ui->actionRestart, ConfigShortcut("DebugRestart"));
  setGlobalShortcut(ui->actionClose, ConfigShortcut("DebugClose"));
  setGlobalShortcut(ui->actionStepInto, ConfigShortcut("DebugStepInto"));
  setGlobalShortcut(ui->actioneStepInto, ConfigShortcut("DebugeStepInto"));
  setGlobalShortcut(ui->actionseStepInto, ConfigShortcut("DebugseStepInto"));
  setGlobalShortcut(ui->actionStepIntoSource,
                    ConfigShortcut("DebugStepIntoSource"));
  setGlobalShortcut(ui->actionStepOver, ConfigShortcut("DebugStepOver"));
  setGlobalShortcut(ui->actioneStepOver, ConfigShortcut("DebugeStepOver"));
  setGlobalShortcut(ui->actionseStepOver, ConfigShortcut("DebugseStepOver"));
  setGlobalShortcut(ui->actionStepOverSource,
                    ConfigShortcut("DebugStepOverSource"));
  setGlobalShortcut(ui->actionRtr, ConfigShortcut("DebugRtr"));
  setGlobalShortcut(ui->actioneRtr, ConfigShortcut("DebugeRtr"));
  setGlobalShortcut(ui->actionRtu, ConfigShortcut("DebugRtu"));
  setGlobalShortcut(ui->actionCommand, ConfigShortcut("DebugCommand"));
  setGlobalShortcut(ui->actionSkipNextInstruction,
                    ConfigShortcut("DebugSkipNextInstruction"));
  setGlobalShortcut(ui->actionTicnd,
                    ConfigShortcut("DebugTraceIntoConditional"));
  setGlobalShortcut(ui->actionTocnd,
                    ConfigShortcut("DebugTraceOverConditional"));
  setGlobalShortcut(ui->actionTRBit,
                    ConfigShortcut("DebugEnableTraceRecordBit"));
  setGlobalShortcut(ui->actionTRNone, ConfigShortcut("DebugTraceRecordNone"));
  setGlobalShortcut(ui->actionInstrUndo, ConfigShortcut("DebugInstrUndo"));
  setGlobalShortcut(ui->actionAnimateInto, ConfigShortcut("DebugAnimateInto"));
  setGlobalShortcut(ui->actionAnimateOver, ConfigShortcut("DebugAnimateOver"));
  setGlobalShortcut(ui->actionAnimateCommand,
                    ConfigShortcut("DebugAnimateCommand"));
  setGlobalShortcut(ui->actionTRTIIT,
                    ConfigShortcut("DebugTraceIntoIntoTracerecord"));
  setGlobalShortcut(ui->actionTRTOIT,
                    ConfigShortcut("DebugTraceOverIntoTracerecord"));
  setGlobalShortcut(ui->actionTRTIBT,
                    ConfigShortcut("DebugTraceIntoBeyondTracerecord"));
  setGlobalShortcut(ui->actionTRTOBT,
                    ConfigShortcut("DebugTraceOverBeyondTracerecord"));

  setGlobalShortcut(ui->actionScylla, ConfigShortcut("PluginsScylla"));

  setGlobalShortcut(actionManageFavourites, ConfigShortcut("FavouritesManage"));

  setGlobalShortcut(ui->actionSettings, ConfigShortcut("OptionsPreferences"));
  setGlobalShortcut(ui->actionAppearance, ConfigShortcut("OptionsAppearance"));
  setGlobalShortcut(ui->actionShortcuts, ConfigShortcut("OptionsShortcuts"));
  setGlobalShortcut(ui->actionTopmost, ConfigShortcut("OptionsTopmost"));
  setGlobalShortcut(ui->actionReloadStylesheet,
                    ConfigShortcut("OptionsReloadStylesheet"));

  setGlobalShortcut(ui->actionAbout, ConfigShortcut("HelpAbout"));
  setGlobalShortcut(ui->actionBlog, ConfigShortcut("HelpBlog"));
  setGlobalShortcut(ui->actionDonate, ConfigShortcut("HelpDonate"));
  setGlobalShortcut(ui->actionCalculator, ConfigShortcut("HelpCalculator"));
  setGlobalShortcut(ui->actionReportBug, ConfigShortcut("HelpReportBug"));
  setGlobalShortcut(ui->actionManual, ConfigShortcut("HelpManual"));
  setGlobalShortcut(ui->actionCrashDump, ConfigShortcut("HelpCrashDump"));

  setGlobalShortcut(ui->actionStrings, ConfigShortcut("ActionFindStrings"));
  setGlobalShortcut(ui->actionCalls,
                    ConfigShortcut("ActionFindIntermodularCalls"));

  for (const MenuEntryInfo& entry : mEntryList)
    if (!entry.hotkeyId.isEmpty())
      entry.mAction->setShortcut(ConfigShortcut(entry.hotkeyId));
}

void MainWindow::updateMRUMenu() {
  QMenu* fileMenu = ui->menuRecentFiles;
  QList<QAction*> list = fileMenu->actions();
  for (int i = 1; i < list.length(); ++i) fileMenu->removeAction(list.at(i));
  mMRUList->appendMenu(fileMenu);
}

void MainWindow::executeCommand() { mCmdLineEdit->execute(); }

QAction* MainWindow::makeCommandAction(QAction* action,
                                       const QString& command) {
  action->setData(QVariant(command));
  connect(action, SIGNAL(triggered()), this, SLOT(execCommandSlot()));
  return action;
}

void MainWindow::execCommandSlot() {
  QAction* action = qobject_cast<QAction*>(sender());
  if (action) DbgCmdExec(action->data().toString());
}

void MainWindow::setFocusToCommandBar() { mCmdLineEdit->setFocus(); }

void MainWindow::execTRBit() {
  mCpuWidget->getDisasmWidget()->ActionTraceRecordBitSlot();
}

void MainWindow::execTRByte() {
  mCpuWidget->getDisasmWidget()->ActionTraceRecordByteSlot();
}

void MainWindow::execTRWord() {
  mCpuWidget->getDisasmWidget()->ActionTraceRecordWordSlot();
}

void MainWindow::execTRNone() {
  mCpuWidget->getDisasmWidget()->ActionTraceRecordDisableSlot();
}

void MainWindow::execTicnd() {
  if (!DbgIsDebugging()) return;

  SimpleTraceDialog mSimpleTraceDialog;
  mSimpleTraceDialog.setTraceCommand("TraceIntoConditional");
  mSimpleTraceDialog.setWindowTitle(tr("Trace into..."));
  mSimpleTraceDialog.setWindowIcon(DIcon("traceinto.png"));
  mSimpleTraceDialog.exec();
}

void MainWindow::execTocnd() {
  if (!DbgIsDebugging()) return;

  SimpleTraceDialog mSimpleTraceDialog;
  mSimpleTraceDialog.setTraceCommand("TraceOverConditional");
  mSimpleTraceDialog.setWindowTitle(tr("Trace over..."));
  mSimpleTraceDialog.setWindowIcon(DIcon("traceover.png"));
  mSimpleTraceDialog.exec();
}

void MainWindow::displayMemMapWidget() { showQWidgetTab(mMemMapView); }

void MainWindow::displayVariables() {
  DbgCmdExec("varlist");
  showQWidgetTab(mReferenceManager);
}

void MainWindow::displayLogWidget() { showQWidgetTab(mLogView); }

void MainWindow::displayScriptWidget() { showQWidgetTab(mScriptView); }

void MainWindow::displayAboutWidget() {
  AboutDialog dialog(mUpdateChecker, this);
  dialog.exec();
}

void MainWindow::openFileSlot() {
  auto filename = QFileDialog::getOpenFileName(
      this, tr("Open file"), mMRUList->getEntry(0),
      tr("All files (*);;Libraries (*.so *.dylib)"));
  if (!filename.length()) return;
  filename = QDir::toNativeSeparators(
      filename);  // convert to native path format (with backlashes)
  openRecentFileSlot(filename);
}

void MainWindow::openPythonFileSlot() {
  auto filename = QFileDialog::getOpenFileName(
      this, tr("Open file"), mMRUList->getEntry(0),
      tr("Python (*.py)"));
  if (!filename.length()) return;
  filename = QDir::toNativeSeparators(
      filename);  // convert to native path format (with backlashes)
  openRecentFileSlot(filename);
}

void MainWindow::openADCppFileSlot() {
  auto filename = QFileDialog::getOpenFileName(
      this, tr("Open file"), mMRUList->getEntry(0),
      tr("ADCpp (*.cc *.mm)"));
  if (!filename.length()) return;
  filename = QDir::toNativeSeparators(
      filename);  // convert to native path format (with backlashes)
  openRecentFileSlot(filename);
}

void MainWindow::openADCppModuleSlot() {
  auto filename = QFileDialog::getOpenFileName(
      this, tr("Open file"), mMRUList->getEntry(0),
      tr("ADCpp (*.adc)"));
  if (!filename.length()) return;
  filename = QDir::toNativeSeparators(
      filename);  // convert to native path format (with backlashes)
  openRecentFileSlot(filename);
}

void MainWindow::openCompileADCppSlot() {
  auto filename = QFileDialog::getOpenFileName(
      this, tr("Open file"), mMRUList->getEntry(0),
      tr("ADCpp (*.cc *.mm)"));
  if (!filename.length()) return;
  filename = QDir::toNativeSeparators(
      filename);  // convert to native path format (with backlashes)
  DbgCmdExec(QString().sprintf("adcc %s", filename.toUtf8().constData()));
}

void MainWindow::openRecentFileSlot(QString filename) {
  DbgCmdExec(QString().sprintf("init \"%s\"", filename.toUtf8().constData()));
}

void MainWindow::runSlot() {
  if (DbgIsDebugging()) DbgCmdExec("run");
  // else
  //   restartDebugging();
}

void MainWindow::pauseSlot() {
  if (DbgIsDebugging()) DbgCmdExec("pause");
}

void MainWindow::restartDebugging() {
  auto last = mMRUList->getEntry(0);
  if (!last.isEmpty()) DbgCmdExec(QString("init \"%1\"").arg(last));
}

void MainWindow::displayBreakpointWidget() { showQWidgetTab(mBreakpointsView); }

void MainWindow::dragEnterEvent(QDragEnterEvent* pEvent) {
  if (pEvent->mimeData()->hasUrls()) {
    pEvent->acceptProposedAction();
  }
}

void MainWindow::dropEvent(QDropEvent* pEvent) {
  if (pEvent->mimeData()->hasUrls()) {
    QString filename =
        QDir::toNativeSeparators(pEvent->mimeData()->urls()[0].toLocalFile());
    DbgCmdExec(QString().sprintf("init \"%s\"", filename.toUtf8().constData()));
    pEvent->acceptProposedAction();
  }
}

bool MainWindow::event(QEvent* event) {
  // just make sure mTabWidget take current view as the latest
  if (event->type() == QEvent::WindowActivate && this->isActiveWindow()) {
    mTabWidget->setCurrentIndex(mTabWidget->currentIndex());
  }

  return QMainWindow::event(event);
}

void MainWindow::updateWindowTitleSlot(QString filename) {
  if (filename.length()) {
    setWindowTitle(filename + QString(" - ") + mWindowMainTitle);
  } else {
    setWindowTitle(mWindowMainTitle);
  }
}

// Used by View->CPU
void MainWindow::displayCpuWidgetShowCpu() {
  showQWidgetTab(mCpuWidget);
  mCpuWidget->setDisasmFocus();
}

// GuiShowCpu()
void MainWindow::displayCpuWidget() { showQWidgetTab(mCpuWidget); }

void MainWindow::displaySymbolWidget() { showQWidgetTab(mSymbolView); }

void MainWindow::displaySourceViewWidget() {
  showQWidgetTab(mSourceViewManager);
}

void MainWindow::displayReferencesWidget() {
  showQWidgetTab(mReferenceManager);
}

void MainWindow::displayThreadsWidget() { showQWidgetTab(mThreadView); }

void MainWindow::displayGraphWidget() {
  showQWidgetTab(mCpuWidget);
  mCpuWidget->setGraphFocus();
}

void MainWindow::displayPreviousTab() { mTabWidget->showPreviousTab(); }

void MainWindow::displayNextTab() { mTabWidget->showNextTab(); }

void MainWindow::displayPreviousView() { mTabWidget->showPreviousView(); }

void MainWindow::displayNextView() { mTabWidget->showNextView(); }

void MainWindow::hideTab() { mTabWidget->deleteCurrentTab(); }

void MainWindow::openSettings() {
  SettingsDialog settings(this);
  connect(&settings, SIGNAL(chkSaveLoadTabOrderStateChanged(bool)), this,
          SLOT(chkSaveloadTabSavedOrderStateChangedSlot(bool)));
  settings.lastException = lastException;
  settings.exec();
}

void MainWindow::openAppearance() {
  AppearanceDialog appearance(this);
  appearance.exec();
}

void MainWindow::openCalculator() {
  mCalculatorDialog->showNormal();
  mCalculatorDialog->setFocus();
  mCalculatorDialog->setExpressionFocus();
}

void MainWindow::openShortcuts() {
  ShortcutsDialog shortcuts(this);
  shortcuts.exec();
}

void MainWindow::changeTopmost(bool checked) {
  if (checked)
    SetWindowPos((HWND)this->winId(), HWND_TOPMOST, 0, 0, 0, 0,
                 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
  else
    SetWindowPos((HWND)this->winId(), HWND_NOTOPMOST, 0, 0, 0, 0,
                 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}

void MainWindow::addRecentFile(QString file) {
  mMRUList->addEntry(file);
  updateMRUMenu();
  mMRUList->save();
}

void MainWindow::setLastException(unsigned int exceptionCode) {
  lastException = exceptionCode;
}

void MainWindow::findStrings() {
  DbgCmdExec(QString(
      "strref " + ToPtrString(mCpuWidget->getDisasmWidget()->getSelectedVa())));
  displayReferencesWidget();
}

void MainWindow::findModularCalls() {
  DbgCmdExec(
      QString("modcallfind " +
              ToPtrString(mCpuWidget->getDisasmWidget()->getSelectedVa())));
  displayReferencesWidget();
}

const MainWindow::MenuInfo* MainWindow::findMenu(int hMenu) {
  if (hMenu == -1) return 0;
  int nFound = -1;
  for (int i = 0; i < mMenuList.size(); i++) {
    if (hMenu == mMenuList.at(i).hMenu) {
      nFound = i;
      break;
    }
  }
  return nFound == -1 ? 0 : &mMenuList.at(nFound);
}

void MainWindow::addMenuToList(QWidget* parent, QMenu* menu, GUIMENUTYPE hMenu,
                               int hParentMenu) {
  if (!findMenu(hMenu))
    mMenuList.push_back(
        MenuInfo(parent, menu, hMenu, hParentMenu, hMenu == GUI_PLUGIN_MENU));
  Bridge::getBridge()->setResult(BridgeResult::MenuAddToList);
}

void MainWindow::addMenu(int hMenu, QString title) {
  const MenuInfo* menu = findMenu(hMenu);
  if (!menu && hMenu != -1) {
    Bridge::getBridge()->setResult(BridgeResult::MenuAdd, -1);
    return;
  }
  int hMenuNew = hEntryMenuPool++;
  QWidget* parent = hMenu == -1 ? this : menu->parent;
  QMenu* wMenu = new QMenu(title, parent);
  wMenu->menuAction()->setVisible(false);
  mMenuList.push_back(
      MenuInfo(parent, wMenu, hMenuNew, hMenu, !menu || menu->globalMenu));
  if (hMenu == -1)  // top-level
    ui->menuBar->addMenu(wMenu);
  else  // deeper level
  {
    menu->mMenu->addMenu(wMenu);
    menu->mMenu->menuAction()->setVisible(true);
  }
  Bridge::getBridge()->setResult(BridgeResult::MenuAdd, hMenuNew);
}

void MainWindow::addMenuEntry(int hMenu, QString title) {
  const MenuInfo* menu = findMenu(hMenu);
  if (!menu && hMenu != -1) {
    Bridge::getBridge()->setResult(BridgeResult::MenuAddEntry, -1);
    return;
  }
  MenuEntryInfo newInfo;
  int hEntryNew = hEntryMenuPool++;
  newInfo.hEntry = hEntryNew;
  newInfo.hParentMenu = hMenu;
  QWidget* parent = hMenu == -1 ? this : menu->parent;
  QAction* wAction = new QAction(title, parent);
  parent->addAction(wAction);
  wAction->setObjectName(QString().sprintf("ENTRY|%d", hEntryNew));
  wAction->setShortcutContext((!menu || menu->globalMenu)
                                  ? Qt::ApplicationShortcut
                                  : Qt::WidgetShortcut);
  parent->addAction(wAction);
  connect(wAction, SIGNAL(triggered()), this, SLOT(menuEntrySlot()));
  newInfo.mAction = wAction;
  mEntryList.push_back(newInfo);
  if (hMenu == -1)  // top level
    ui->menuBar->addAction(wAction);
  else  // deeper level
  {
    menu->mMenu->addAction(wAction);
    menu->mMenu->menuAction()->setVisible(true);
  }
  Bridge::getBridge()->setResult(BridgeResult::MenuAddEntry, hEntryNew);
}

void MainWindow::addSeparator(int hMenu) {
  const MenuInfo* menu = findMenu(hMenu);
  if (menu) {
    MenuEntryInfo newInfo;
    newInfo.hEntry = -1;
    newInfo.hParentMenu = hMenu;
    newInfo.mAction = menu->mMenu->addSeparator();
    mEntryList.push_back(newInfo);
  }
  Bridge::getBridge()->setResult(BridgeResult::MenuAddSeparator);
}

void MainWindow::clearMenuHelper(int hMenu) {
  // delete menu entries
  for (auto i = mEntryList.size() - 1; i != -1; i--)
    if (hMenu ==
        mEntryList.at(i)
            .hParentMenu)  // we found an entry that has the menu as parent
      mEntryList.erase(mEntryList.begin() + i);
  // delete the menus
  std::vector<int> menuClearQueue;
  for (auto i = mMenuList.size() - 1; i != -1; i--) {
    if (hMenu ==
        mMenuList.at(i)
            .hParentMenu)  // we found a menu that has the menu as parent
    {
      menuClearQueue.push_back(mMenuList.at(i).hMenu);
      mMenuList.erase(mMenuList.begin() + i);
    }
  }
  // recursively clear the menus
  for (auto& hMenu : menuClearQueue) clearMenuHelper(hMenu);
}

void MainWindow::clearMenuImpl(int hMenu, bool erase) {
  // this recursively removes the entries from mEntryList and mMenuList
  clearMenuHelper(hMenu);
  for (auto it = mMenuList.begin(); it != mMenuList.end(); ++it) {
    auto& curMenu = *it;
    if (hMenu == curMenu.hMenu) {
      if (erase) {
        auto parentMenu = findMenu(curMenu.hParentMenu);
        if (parentMenu) {
          parentMenu->mMenu->removeAction(
              curMenu.mMenu->menuAction());  // remove the QMenu from the parent
          if (parentMenu->mMenu->actions()
                  .empty())  // hide the parent if it is now empty
            parentMenu->mMenu->menuAction()->setVisible(false);
        }
        it = mMenuList.erase(it);
      } else {
        curMenu.mMenu->clear();  // clear the QMenu
        curMenu.mMenu->menuAction()->setVisible(false);
      }
      break;
    }
  }
}

void MainWindow::clearMenu(int hMenu, bool erase) {
  clearMenuImpl(hMenu, erase);
  Bridge::getBridge()->setResult(BridgeResult::MenuClear);
}

void MainWindow::initMenuApi() {
  // 256 entries are reserved
  hEntryMenuPool = 256;
  mEntryList.reserve(1024);
  mMenuList.reserve(1024);
}

void MainWindow::menuEntrySlot() {
  QAction* action = qobject_cast<QAction*>(sender());
  if (action && action->objectName().startsWith("ENTRY|")) {
    int hEntry = -1;
    if (sscanf_s(action->objectName().mid(6).toUtf8().constData(), "%d",
                 &hEntry) == 1)
      DbgMenuEntryClicked(hEntry);
  }
}

void MainWindow::removeMenuEntry(int hEntryMenu) {
  // find and remove the hEntryMenu from the mEntryList
  for (int i = 0; i < mEntryList.size(); i++) {
    if (mEntryList.at(i).hEntry == hEntryMenu) {
      auto& entry = mEntryList.at(i);
      auto parentMenu = findMenu(entry.hParentMenu);
      if (parentMenu) {
        parentMenu->mMenu->removeAction(entry.mAction);
        if (parentMenu->mMenu->actions().empty())
          parentMenu->mMenu->menuAction()->setVisible(false);
        mEntryList.erase(mEntryList.begin() + i);
      }
      Bridge::getBridge()->setResult(BridgeResult::MenuRemove);
      return;
    }
  }
  // if hEntryMenu is not in mEntryList, clear+erase it from mMenuList
  clearMenuImpl(hEntryMenu, true);
  Bridge::getBridge()->setResult(BridgeResult::MenuRemove);
}

void MainWindow::setIconMenuEntry(int hEntry, QIcon icon) {
  for (int i = 0; i < mEntryList.size(); i++) {
    if (mEntryList.at(i).hEntry == hEntry) {
      const MenuEntryInfo& entry = mEntryList.at(i);
      entry.mAction->setIcon(icon);
      break;
    }
  }
  Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryIcon);
}

void MainWindow::setIconMenu(int hMenu, QIcon icon) {
  for (int i = 0; i < mMenuList.size(); i++) {
    if (mMenuList.at(i).hMenu == hMenu) {
      const MenuInfo& menu = mMenuList.at(i);
      menu.mMenu->setIcon(icon);
    }
  }
  Bridge::getBridge()->setResult(BridgeResult::MenuSetIcon);
}

void MainWindow::setCheckedMenuEntry(int hEntry, bool checked) {
  for (int i = 0; i < mEntryList.size(); i++) {
    if (mEntryList.at(i).hEntry == hEntry) {
      const MenuEntryInfo& entry = mEntryList.at(i);
      entry.mAction->setCheckable(true);
      entry.mAction->setChecked(checked);
      break;
    }
  }
  Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryChecked);
}

QString MainWindow::nestedMenuDescription(const MenuInfo* menu) {
  auto found = findMenu(menu->hParentMenu);
  if (!found) return menu->mMenu->title();
  auto nest = nestedMenuDescription(found);
  if (nest.isEmpty()) {
    switch (menu->hParentMenu) {
      case GUI_DISASM_MENU:
        nest = tr("&Plugins") + " -> " + tr("Disassembly");
        break;
      case GUI_DUMP_MENU:
        nest = tr("&Plugins") + " -> " + tr("Dump");
        break;
      case GUI_STACK_MENU:
        nest = tr("&Plugins") + " -> " + tr("Stack");
        break;
    }
  }
  nest += " -> ";
  return nest + menu->mMenu->title();
}

QString MainWindow::nestedMenuEntryDescription(const MenuEntryInfo& entry) {
  return QString(nestedMenuDescription(findMenu(entry.hParentMenu)) + " -> " +
                 entry.mAction->text())
      .replace("&", "");
}

void MainWindow::setHotkeyMenuEntry(int hEntry, QString hotkey, QString id) {
  for (int i = 0; i < mEntryList.size(); i++) {
    if (mEntryList.at(i).hEntry == hEntry) {
      MenuEntryInfo& entry = mEntryList[i];
      entry.hotkeyId = QString("Plugin_") + id;
      id.truncate(id.lastIndexOf('_'));
      entry.hotkey = hotkey;
      entry.hotkeyGlobal =
          entry.mAction->shortcutContext() == Qt::ApplicationShortcut;
      Config()->setPluginShortcut(entry.hotkeyId,
                                  nestedMenuEntryDescription(entry), hotkey,
                                  entry.hotkeyGlobal);
      refreshShortcuts();
      break;
    }
  }
  Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryHotkey);
}

void MainWindow::setVisibleMenuEntry(int hEntry, bool visible) {
  for (int i = 0; i < mEntryList.size(); i++) {
    if (mEntryList.at(i).hEntry == hEntry) {
      const MenuEntryInfo& entry = mEntryList.at(i);
      entry.mAction->setVisible(visible);
      break;
    }
  }
  Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryVisible);
}

void MainWindow::setVisibleMenu(int hMenu, bool visible) {
  for (int i = 0; i < mMenuList.size(); i++) {
    if (mMenuList.at(i).hMenu == hMenu) {
      const MenuInfo& menu = mMenuList.at(i);
      menu.mMenu->setVisible(visible);
    }
  }
  Bridge::getBridge()->setResult(BridgeResult::MenuSetVisible);
}

void MainWindow::setNameMenuEntry(int hEntry, QString name) {
  for (int i = 0; i < mEntryList.size(); i++) {
    if (mEntryList.at(i).hEntry == hEntry) {
      const MenuEntryInfo& entry = mEntryList.at(i);
      entry.mAction->setText(name);
      Config()->setPluginShortcut(entry.hotkeyId,
                                  nestedMenuEntryDescription(entry),
                                  entry.hotkey, entry.hotkeyGlobal);
      break;
    }
  }
  Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryName);
}

void MainWindow::setNameMenu(int hMenu, QString name) {
  for (int i = 0; i < mMenuList.size(); i++) {
    if (mMenuList.at(i).hMenu == hMenu) {
      const MenuInfo& menu = mMenuList.at(i);
      menu.mMenu->setTitle(name);
    }
  }
  Bridge::getBridge()->setResult(BridgeResult::MenuSetName);
}

void MainWindow::runSelection() {
  if (DbgIsDebugging())
    DbgCmdExec(("run " + ToPtrString(mCpuWidget->getSelectionVa())));
}

void MainWindow::runExpression() {
  if (!DbgIsDebugging()) return;

  GotoDialog gotoDialog(this);
  gotoDialog.setWindowTitle(tr("Enter expression to run to..."));
  if (gotoDialog.exec() != QDialog::Accepted) return;

  if (DbgCmdExecDirect(QString("bp \"%1\", ss")
                           .arg(gotoDialog.expressionText)
                           .toUtf8()
                           .constData()))
    DbgCmdExecDirect("run");
}

void MainWindow::getStrWindow(const QString title, QString* text) {
  LineEditDialog mLineEdit(this);
  mLineEdit.setWindowTitle(title);
  bool bResult = true;
  if (mLineEdit.exec() != QDialog::Accepted) bResult = false;
  *text = mLineEdit.editText;
  Bridge::getBridge()->setResult(BridgeResult::GetlineWindow, bResult);
}

void MainWindow::displayComments() {
  if (!DbgIsDebugging()) return;
  DbgCmdExec("commentlist");
  displayReferencesWidget();
}

void MainWindow::displayLabels() {
  if (!DbgIsDebugging()) return;
  DbgCmdExec("labellist");
  displayReferencesWidget();
}

void MainWindow::displayBookmarks() {
  if (!DbgIsDebugging()) return;
  DbgCmdExec("bookmarklist");
  displayReferencesWidget();
}

void MainWindow::displayFunctions() {
  if (!DbgIsDebugging()) return;
  DbgCmdExec("functionlist");
  displayReferencesWidget();
}

void MainWindow::displayCallstack() { showQWidgetTab(mCallStackView); }

void MainWindow::displaySEHChain() { showQWidgetTab(mSEHChainView); }

void MainWindow::displayRunTrace() { showQWidgetTab(mTraceWidget); }

void MainWindow::donate() {
#if 0
  QMessageBox msg(QMessageBox::Information, tr("Donate"),
                  tr("All the money will go to x64dbg development."));
  msg.setWindowIcon(DIcon("donate.png"));
  msg.setParent(this, Qt::Dialog);
  msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
  msg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
  msg.setDefaultButton(QMessageBox::Ok);
  if (msg.exec() != QMessageBox::Ok) return;
  QDesktopServices::openUrl(QUrl("http://donate.yunyoo.cn"));
#endif
}

void MainWindow::blog() {
#if 0
  QMessageBox msg(QMessageBox::Information, tr("Blog"),
                  tr("You will visit x64dbg's official blog."));
  msg.setWindowIcon(DIcon("hex.png"));
  msg.setParent(this, Qt::Dialog);
  msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
  msg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
  msg.setDefaultButton(QMessageBox::Ok);
  if (msg.exec() != QMessageBox::Ok) return;
  QDesktopServices::openUrl(QUrl("http://blog.yunyoo.cn"));
#else
  QDesktopServices::openUrl(
      QUrl("https://mp.weixin.qq.com/s/yNGJ_p6sTnGIMaa3XJaNbw"));
#endif
}

void MainWindow::reportBug() {
#if 0
  QMessageBox msg(
      QMessageBox::Information, tr("Report Bug"),
      tr("You will be taken to a website where you can report a bug.\nMake "
         "sure to fill in as much information as possible."));
  msg.setWindowIcon(DIcon("bug-report.png"));
  msg.setParent(this, Qt::Dialog);
  msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
  msg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
  msg.setDefaultButton(QMessageBox::Ok);
  if (msg.exec() != QMessageBox::Ok) return;
  QDesktopServices::openUrl(QUrl("http://report.yunyoo.cn"));
#else
  QDesktopServices::openUrl(QUrl("https://gitee.com/geekneo/A64Dbg/issues"));
#endif
}

void MainWindow::crashDump() {
#if 0
  QMessageBox msg(
      QMessageBox::Critical, tr("Generate crash dump"),
      tr("This action will crash the debugger and generate a crash dump. You "
         "will LOSE ALL YOUR UNSAVED DATA. Do you really want to continue?"));
  msg.setWindowIcon(DIcon("fatal-error.png"));
  msg.setParent(this, Qt::Dialog);
  msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
  msg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
  msg.setDefaultButton(QMessageBox::Cancel);
  if (msg.exec() != QMessageBox::Ok) return;

  // Fatal error
  __debugbreak();

  // Congratulations! We survived a fatal error!
  SimpleWarningBox(this, tr("Have fun debugging the debugger!"),
                   tr("Debugger detected!"));
#endif
}

void MainWindow::displayAttach() {
  AttachDialog attach(this);
  attach.exec();

  mCpuWidget->setDisasmFocus();
}

void MainWindow::launchiOSAppSlot() {
  LaunchDialog launch('ios', this);
  launch.exec();

  mCpuWidget->setDisasmFocus();
}

void MainWindow::launchApkAppSlot() {
  LaunchDialog launch('apk', this);
  launch.exec();

  mCpuWidget->setDisasmFocus();
}

static bool getCmdLine(QString& cmdLine) {
  auto result = false;
  size_t cbsize = 0;
  if (DbgFunctions()->GetCmdline(0, &cbsize)) {
    auto buffer = new char[cbsize];
    if (DbgFunctions()->GetCmdline(buffer, 0)) {
      cmdLine = buffer;
      result = true;
    }
    delete[] buffer;
  }
  return result;
}

void MainWindow::changeCommandLine() {
  if (!DbgIsDebugging()) return;

  LineEditDialog mLineEdit(this);
  mLineEdit.setText("");
  mLineEdit.setWindowTitle(tr("Change Command Line"));
  mLineEdit.setWindowIcon(DIcon("changeargs.png"));

  QString cmdLine;
  if (!getCmdLine(cmdLine))
    mLineEdit.setText(
        tr("Cannot get remote command line, use the 'getcmdline' command for "
           "more information."));
  else
    mLineEdit.setText(cmdLine);

  mLineEdit.setCursorPosition(0);

  if (mLineEdit.exec() != QDialog::Accepted) return;  // pressed cancel

  if (!DbgFunctions()->SetCmdline(
          (char*)mLineEdit.editText.toUtf8().constData()))
    SimpleErrorBox(this, tr("Error!"), tr("Could not set command line!"));
  else {
    DbgFunctions()->MemUpdateMap();
    GuiUpdateMemoryView();
    getCmdLine(cmdLine);
    GuiAddLogMessage(
        (tr("New command line: ") + cmdLine + "\n").toUtf8().constData());
  }
}

void MainWindow::displayManual() {
  duint setting = 0;
  if (BridgeSettingGetUint("Misc", "UseLocalHelpFile", &setting) && setting) {
    // Open the Windows CHM in the upper directory
    if (!QDesktopServices::openUrl(QUrl(QUrl::fromLocalFile(
            QString("%1/../x64dbg.chm")
                .arg(QCoreApplication::applicationDirPath())))))
      SimpleErrorBox(
          this, tr("Error"),
          tr("Manual cannot be opened. Please check if x64dbg.chm exists and "
             "ensure there is no other problems with your system."));
  } else
    QDesktopServices::openUrl(QUrl("http://help.yunyoo.cn"));
}

void MainWindow::canClose() {
  bCanClose = true;
  close();
}

void MainWindow::addQWidgetTab(QWidget* qWidget, QString nativeName) {
  mTabWidget->addTabEx(qWidget, qWidget->windowIcon(), qWidget->windowTitle(),
                       nativeName);
}

void MainWindow::addQWidgetTab(QWidget* qWidget) {
  WidgetInfo info(qWidget, qWidget->metaObject()->className());
  addQWidgetTab(info.widget, info.nativeName);
  mPluginWidgetList.append(info);
}

void MainWindow::showQWidgetTab(QWidget* qWidget) {
  qWidget->show();
  qWidget->setFocus();
  setTab(qWidget);
}

void MainWindow::closeQWidgetTab(QWidget* qWidget) {
  for (int i = 0; i < mTabWidget->count(); i++) {
    if (mTabWidget->widget(i) == qWidget) {
      mTabWidget->DeleteTab(i);
      break;
    }
  }
}

void MainWindow::executeOnGuiThread(void* cbGuiThread, void* userdata) {
  ((GUICALLBACKEX)cbGuiThread)(userdata);
}

void MainWindow::tabMovedSlot(int from, int to) {
  Q_UNUSED(from);
  Q_UNUSED(to);
  for (int i = 0; i < mTabWidget->count(); i++) {
    // Remove space in widget name and append Tab to get config settings
    // (CPUTab, MemoryMapTab, etc...)
    // QString tabName = mTabWidget->tabText(i).replace(" ", "") + "Tab";
    QString tabName = mTabWidget->getNativeName(i);
    auto found = std::find_if(mWidgetList.begin(), mWidgetList.end(),
                              [&tabName](const WidgetInfo& info) {
                                return info.nativeName == tabName;
                              });
    if (found != mWidgetList.end()) Config()->setUint("TabOrder", tabName, i);
  }
}

void MainWindow::chkSaveloadTabSavedOrderStateChangedSlot(bool state) {
  if (state)
    loadTabSavedOrder();
  else
    loadTabDefaultOrder();
}

void MainWindow::dbgStateChangedSlot(DBGSTATE state) {
  if (state == initialized)  // fixes a crash when restarting with certain
                             // settings in another tab
    displayCpuWidget();

  adp_event_t event;
  switch (state) {
    case initialized:
      event = adp_event_debug_initialized;
      break;
    case running:
      event = adp_event_debug_running;
      break;
    case paused:
      event = adp_event_debug_paused;
      break;
    case stopped:
      event = adp_event_debug_terminated;
      break;
    default:
      return;
  }
  ADPluginManager::inst()->sendEvent(event);
}

void MainWindow::on_actionFaq_triggered() {
  QDesktopServices::openUrl(
      QUrl("https://gitee.com/yunyoo/adusrmanual"));
}

void MainWindow::on_actionReloadStylesheet_triggered() {
  loadSelectedStyle(true);
  ensurePolished();
  update();
}

void MainWindow::displayNotesWidget() { showQWidgetTab(mNotesManager); }

void MainWindow::displayHandlesWidget() { showQWidgetTab(mHandlesView); }

void MainWindow::manageFavourites() {
  FavouriteTools favToolsDialog(this);
  favToolsDialog.exec();
  updateFavouriteTools();
}

static void splitToolPath(const QString& toolPath, QString& file,
                          QString& cmd) {
  if (toolPath.startsWith('\"')) {
    auto endQuote = toolPath.indexOf('\"', 1);
    if (endQuote == -1)  //"failure with spaces
      file = toolPath.mid(1);
    else  //"path with spaces" arguments
    {
      file = toolPath.mid(1, endQuote - 1);
      cmd = toolPath.mid(endQuote + 1);
    }
  } else {
    auto firstSpace = toolPath.indexOf(' ');
    if (firstSpace == -1)  // pathwithoutspaces
      file = toolPath;
    else  // pathwithoutspaces argument
    {
      file = toolPath.left(firstSpace);
      cmd = toolPath.mid(firstSpace + 1);
    }
  }
  file = file.trimmed();
  cmd = cmd.trimmed();
}

void MainWindow::clickPluginMenu() {
  QAction* action = (QAction*)sender();
  if (!action) return;

  QVariant data = action->data();
  ADPluginInstance& adp = ADPluginManager::inst()->adps[data.toInt()];
  adp.invoke(adp_event_main_menu);
}

void MainWindow::addPluginMenu() {
  int iconid = 0;
  for (auto& adp : ADPluginManager::inst()->adps) {
    QAction* newAction = new QAction(ui->actionPlugins);
    newAction->setIcon(DIcon(QString("christmas%1.png").arg(1 + (iconid++) % 8)));
    newAction->setText(adp.name().data());
    newAction->setData(
        QVariant((int)(&adp - &ADPluginManager::inst()->adps[0])));
    connect(newAction, SIGNAL(triggered()), this, SLOT(clickPluginMenu()));
    ui->menuPlugins->addAction(newAction);
  }
}

void MainWindow::updateFavouriteTools() {
  char buffer[MAX_SETTING_SIZE];
  bool isanythingexists = false;
  ui->menuFavourites->clear();
  delete actionManageFavourites;
  mFavouriteToolbar->clear();
  actionManageFavourites =
      new QAction(DIcon("star.png"), tr("&Manage Favourite Tools..."), this);
  for (unsigned int i = 1; BridgeSettingGet(
           "Favourite", QString("Tool%1").arg(i).toUtf8().constData(), buffer);
       i++) {
    QString toolPath = QString(buffer);
    QAction* newAction =
        new QAction(actionManageFavourites);  // Auto delete these actions on
                                              // updateFavouriteTools()
    // Set up user data to be used in clickFavouriteTool()
    newAction->setData(QVariant(QString("Tool,%1").arg(toolPath)));
    if (BridgeSettingGet("Favourite",
                         QString("ToolShortcut%1").arg(i).toUtf8().constData(),
                         buffer))
      if (*buffer && strcmp(buffer, "NOT_SET") != 0)
        setGlobalShortcut(newAction, QKeySequence(QString(buffer)));
    if (BridgeSettingGet(
            "Favourite",
            QString("ToolDescription%1").arg(i).toUtf8().constData(), buffer))
      newAction->setText(QString(buffer));
    else
      newAction->setText(toolPath);
    // Get the icon of the executable
    QString file, cmd;
    QIcon icon;
    splitToolPath(toolPath, file, cmd);
    icon = getFileIcon(file);
    if (icon.isNull()) icon = DIcon("plugin.png");
    newAction->setIcon(icon);
    connect(newAction, SIGNAL(triggered()), this, SLOT(clickFavouriteTool()));
    ui->menuFavourites->addAction(newAction);
    mFavouriteToolbar->addAction(newAction);
    isanythingexists = true;
  }
  if (isanythingexists) {
    isanythingexists = false;
    ui->menuFavourites->addSeparator();
    mFavouriteToolbar->addSeparator();
  }
  for (unsigned int i = 1; BridgeSettingGet(
           "Favourite", QString("Script%1").arg(i).toUtf8().constData(),
           buffer);
       i++) {
    QString scriptPath = QString(buffer);
    QAction* newAction = new QAction(actionManageFavourites);
    // Set up user data to be used in clickFavouriteTool()
    newAction->setData(QVariant(QString("Script,%1").arg(scriptPath)));
    if (BridgeSettingGet(
            "Favourite",
            QString("ScriptShortcut%1").arg(i).toUtf8().constData(), buffer))
      if (*buffer && strcmp(buffer, "NOT_SET") != 0)
        setGlobalShortcut(newAction, QKeySequence(QString(buffer)));
    if (BridgeSettingGet(
            "Favourite",
            QString("ScriptDescription%1").arg(i).toUtf8().constData(), buffer))
      newAction->setText(QString(buffer));
    else
      newAction->setText(scriptPath);
    connect(newAction, SIGNAL(triggered()), this, SLOT(clickFavouriteTool()));
    newAction->setIcon(DIcon("script-code.png"));
    ui->menuFavourites->addAction(newAction);
    mFavouriteToolbar->addAction(newAction);
    isanythingexists = true;
  }
  if (isanythingexists) {
    isanythingexists = false;
    ui->menuFavourites->addSeparator();
    mFavouriteToolbar->addSeparator();
  }
  for (unsigned int i = 1; BridgeSettingGet(
           "Favourite", QString("Command%1").arg(i).toUtf8().constData(),
           buffer);
       i++) {
    QAction* newAction = new QAction(QString(buffer), actionManageFavourites);
    // Set up user data to be used in clickFavouriteTool()
    newAction->setData(QVariant(QString("Command")));
    if (BridgeSettingGet(
            "Favourite",
            QString("CommandShortcut%1").arg(i).toUtf8().constData(), buffer))
      if (*buffer && strcmp(buffer, "NOT_SET") != 0)
        setGlobalShortcut(newAction, QKeySequence(QString(buffer)));
    connect(newAction, SIGNAL(triggered()), this, SLOT(clickFavouriteTool()));
    newAction->setIcon(DIcon("star.png"));
    ui->menuFavourites->addAction(newAction);
    mFavouriteToolbar->addAction(newAction);
    isanythingexists = true;
  }
  if (isanythingexists) {
    ui->menuFavourites->addSeparator();
    mFavouriteToolbar->addSeparator();
  }
  ui->menuFavourites->addAction(actionManageFavourites);
  setGlobalShortcut(actionManageFavourites, ConfigShortcut("FavouritesManage"));
  connect(ui->menuFavourites->actions().last(), SIGNAL(triggered()), this,
          SLOT(manageFavourites()));
  mFavouriteToolbar->setSizePolicy(QSizePolicy::Expanding,
                                   QSizePolicy::Expanding);
  ui->menuFavourites->addAction(mFavouriteToolbar->toggleViewAction());
}

static QString stringFormatInline(const QString& format) {
  if (!DbgFunctions()->StringFormatInline) return QString();
  char result[MAX_SETTING_SIZE] = "";
  if (DbgFunctions()->StringFormatInline(format.toUtf8().constData(),
                                         MAX_SETTING_SIZE, result))
    return result;
  return CPUArgumentWidget::tr("[Formatting Error]");
}

void MainWindow::clickFavouriteTool() {
  QAction* action = qobject_cast<QAction*>(sender());
  if (action == nullptr) return;
  QString data = action->data().toString();
  if (data.startsWith("Tool,")) {
    QString toolPath = data.mid(5);
    duint PID = DbgValFromString("$pid");
    toolPath.replace(QString("%PID%"), QString::number(PID),
                     Qt::CaseInsensitive);
    toolPath.replace(QString("%DEBUGGEE%"), mMRUList->getEntry(0),
                     Qt::CaseInsensitive);
    char modpath[MAX_MODULE_SIZE] = "";
    DbgFunctions()->ModPathFromAddr(DbgValFromString("dis.sel()"), modpath,
                                    MAX_MODULE_SIZE);
    toolPath.replace(QString("%MODULE%"), modpath, Qt::CaseInsensitive);
    while (true) {
      auto sfStart = toolPath.indexOf("%-");
      auto sfEnd = toolPath.indexOf("-%");
      if (sfStart < 0 || sfEnd < 0 || sfEnd < sfStart) break;
      auto format = toolPath.mid(sfStart + 2, sfEnd - sfStart - 2);
      toolPath.replace(sfStart, sfEnd - sfStart + 2,
                       stringFormatInline(format));
    }
    mLastLogLabel->setText(toolPath);
#ifdef _WIN32
    PROCESS_INFORMATION procinfo;
    STARTUPINFO startupinfo;
    memset(&procinfo, 0, sizeof(PROCESS_INFORMATION));
    memset(&startupinfo, 0, sizeof(startupinfo));
    startupinfo.cb = sizeof(startupinfo);
    if (CreateProcessW(nullptr, (LPWSTR)toolPath.toStdWString().c_str(),
                       nullptr, nullptr, FALSE, 0, nullptr, nullptr,
                       &startupinfo, &procinfo)) {
      CloseHandle(procinfo.hThread);
      CloseHandle(procinfo.hProcess);
    } else if (GetLastError() == ERROR_ELEVATION_REQUIRED) {
#if 0
      QString file, cmd;
      splitToolPath(toolPath, file, cmd);
      ShellExecuteW(nullptr, L"runas", file.toStdWString().c_str(),
                    cmd.toStdWString().c_str(), nullptr, SW_SHOWNORMAL);
#endif
    }
#endif
  } else if (data.startsWith("Script,")) {
    QString scriptPath = data.mid(7);
    mScriptView->openRecentFile(scriptPath);
    displayScriptWidget();
  } else if (data.compare("Command") == 0) {
    DbgCmdExec(action->text());
  }
}

void MainWindow::chooseLanguage() {
  QAction* action = qobject_cast<QAction*>(sender());
  QString localeName = action->text();
  localeName = localeName.mid(1, localeName.indexOf(QChar(']')) - 1);
  action->setChecked(localeName == QString(currentLocale));
  if (localeName != "en_US") {
    QDir translationsDir(QString("%1/../translations/")
                             .arg(QCoreApplication::applicationDirPath()));
    QFile file(translationsDir.absoluteFilePath(
        QString("x64dbg_%1.qm").arg(localeName)));
    if (file.size() < 512) {
      QMessageBox msg(this);
      msg.setWindowIcon(DIcon("codepage.png"));
      msg.setIcon(QMessageBox::Information);
      if (tr("Languages") == QString("Languages")) {
        msg.setWindowTitle(QString("Languages"));
        msg.setText(
            QString("The translation is nearly empty. Do you still want to use "
                    "this language?"));
      } else {
        msg.setWindowTitle(tr("Languages") + QString(" Languages"));
        msg.setText(tr("The translation is nearly empty. Do you still want to "
                       "use this language?") +
                    QString("\r\nThe translation is nearly empty. Do you still "
                            "want to use this language?"));
      }
      msg.addButton(QMessageBox::Yes);
      msg.addButton(QMessageBox::No);
      if (msg.exec() == QMessageBox::No) return;
    }
  }
  BridgeSettingSet("Engine", "Language", localeName.toUtf8().constData());
  QMessageBox msg(this);
  msg.setIcon(QMessageBox::Information);
  msg.setWindowIcon(DIcon("codepage.png"));
  if (tr("Languages") == QString("Languages")) {
    msg.setWindowTitle(QString("Languages"));
    msg.setText(QString("New language setting will take effect upon restart."));
  } else {
    msg.setWindowTitle(tr("Languages") + QString(" Languages"));
    msg.setText(
        tr("New language setting will take effect upon restart.") +
        QString("\r\nNew language setting will take effect upon restart."));
  }
  msg.exec();
}

void MainWindow::addFavouriteItem(int type, const QString& name,
                                  const QString& description) {
  if (type == 0)  // Tools
  {
    char buffer[MAX_SETTING_SIZE];
    unsigned int i;
    for (i = 1; BridgeSettingGet(
             "Favourite",
             (QString("Tool") + QString::number(i)).toUtf8().constData(),
             buffer);
         i++) {
    }
    BridgeSettingSet(
        "Favourite",
        (QString("Tool") + QString::number(i)).toUtf8().constData(),
        name.toUtf8().constData());
    BridgeSettingSet(
        "Favourite",
        (QString("ToolDescription") + QString::number(i)).toUtf8().constData(),
        description.toUtf8().constData());
    if (BridgeSettingGet(
            "Favourite",
            (QString("Tool") + QString::number(i + 1)).toUtf8().constData(),
            buffer)) {
      buffer[0] = 0;
      BridgeSettingSet(
          "Favourite",
          (QString("Tool") + QString::number(i + 1)).toUtf8().constData(),
          buffer);
    }
    updateFavouriteTools();
  } else if (type == 2)  // Commands
  {
    char buffer[MAX_SETTING_SIZE];
    unsigned int i;
    for (i = 1; BridgeSettingGet(
             "Favourite",
             (QString("Command") + QString::number(i)).toUtf8().constData(),
             buffer);
         i++) {
    }
    BridgeSettingSet(
        "Favourite",
        (QString("Command") + QString::number(i)).toUtf8().constData(),
        name.toUtf8().constData());
    BridgeSettingSet(
        "Favourite",
        (QString("CommandShortcut") + QString::number(i)).toUtf8().constData(),
        description.toUtf8().constData());
    if (BridgeSettingGet(
            "Favourite",
            (QString("Command") + QString::number(i + 1)).toUtf8().constData(),
            buffer)) {
      buffer[0] = 0;
      BridgeSettingSet(
          "Favourite",
          (QString("Command") + QString::number(i + 1)).toUtf8().constData(),
          buffer);
    }
    updateFavouriteTools();
  }
}

void MainWindow::setFavouriteItemShortcut(int type, const QString& name,
                                          const QString& shortcut) {
  if (type == 0) {
    char buffer[MAX_SETTING_SIZE];
    for (unsigned int i = 1; BridgeSettingGet(
             "Favourite", QString("Tool%1").arg(i).toUtf8().constData(),
             buffer);
         i++) {
      if (QString(buffer) == name) {
        BridgeSettingSet(
            "Favourite",
            (QString("ToolShortcut") + QString::number(i)).toUtf8().constData(),
            shortcut.toUtf8().constData());
        updateFavouriteTools();
        break;
      }
    }
  }
}

void MainWindow::animateIntoSlot() {
  if (DbgIsDebugging()) DbgFunctions()->AnimateCommand("StepInto;AnimateWait");
}

void MainWindow::animateOverSlot() {
  if (DbgIsDebugging()) DbgFunctions()->AnimateCommand("StepOver;AnimateWait");
}

void MainWindow::animateCommandSlot() {
  QString command;
  if (SimpleInputBox(this, tr("Animate command"), "", command,
                     tr("Example: StepInto")))
    DbgFunctions()->AnimateCommand(command.toUtf8().constData());
}

void MainWindow::setInitializationScript() {
  SystemBreakpointScriptDialog dialog(this);
  dialog.exec();
}

void MainWindow::customizeMenu() {
  CustomizeMenuDialog customMenuDialog(this);
  customMenuDialog.setWindowTitle(tr("Customize Menus"));
  customMenuDialog.setWindowIcon(DIcon("analysis.png"));
  customMenuDialog.exec();
  onMenuCustomized();
}

void MainWindow::on_actionImportSettings_triggered() {
  auto filename = QFileDialog::getOpenFileName(
      this, tr("Open file"), QCoreApplication::applicationDirPath(),
      tr("Settings (*.ini);;All files (*.*)"));
  if (!filename.length()) return;
  importSettings(filename);
}

void MainWindow::on_actionImportdatabase_triggered() {
  if (!DbgIsDebugging()) return;
  auto filename = QFileDialog::getOpenFileName(
      this, tr("Import database"), QString(),
      tr("Databases (%1);;All files (*.*)").arg(ArchValue("*.dd32", "*.dd64")));
  if (!filename.length()) return;
  DbgCmdExec(QString("dbload \"%1\"").arg(QDir::toNativeSeparators(filename)));
}

void MainWindow::on_actionExportdatabase_triggered() {
  if (!DbgIsDebugging()) return;
  auto filename = QFileDialog::getSaveFileName(
      this, tr("Export database"), QString(),
      tr("Databases (%1);;All files (*.*)").arg(ArchValue("*.dd32", "*.dd64")));
  if (!filename.length()) return;
  DbgCmdExec(QString("dbsave \"%1\"").arg(QDir::toNativeSeparators(filename)));
}

static void setupMenuCustomizationHelper(QMenu* parentMenu,
                                         QList<QAction*>& stringList) {
  for (int i = 0; i < parentMenu->actions().size(); i++) {
    QAction* action = parentMenu->actions().at(i);
    stringList.append(action);
  }
}

void MainWindow::setupMenuCustomization() {
  mFileMenuStrings.append(new QAction("File", this));
  setupMenuCustomizationHelper(ui->menuFile, mFileMenuStrings);
  mDebugMenuStrings.append(new QAction("Debug", this));
  setupMenuCustomizationHelper(ui->menuDebug, mDebugMenuStrings);
  mOptionsMenuStrings.append(new QAction("Option", this));
  setupMenuCustomizationHelper(ui->menuOptions, mOptionsMenuStrings);
  mHelpMenuStrings.append(new QAction("Help", this));
  setupMenuCustomizationHelper(ui->menuHelp, mHelpMenuStrings);
  mViewMenuStrings.append(new QAction("View", this));
  setupMenuCustomizationHelper(ui->menuView, mViewMenuStrings);
  onMenuCustomized();
  Config()->registerMainMenuStringList(&mFileMenuStrings);
  Config()->registerMainMenuStringList(&mDebugMenuStrings);
  Config()->registerMainMenuStringList(&mOptionsMenuStrings);
  Config()->registerMainMenuStringList(&mHelpMenuStrings);
  Config()->registerMainMenuStringList(&mViewMenuStrings);
}

void MainWindow::onMenuCustomized() {
  QList<QMenu*> menus;
  QList<QString> menuNativeNames;
  QList<QList<QAction*>*> menuTextStrings;
  menus << ui->menuFile << ui->menuDebug << ui->menuOptions << ui->menuHelp
        << ui->menuView;
  menuNativeNames << "File"
                  << "Debug"
                  << "Option"
                  << "Help"
                  << "View";
  menuTextStrings << &mFileMenuStrings << &mDebugMenuStrings
                  << &mOptionsMenuStrings << &mHelpMenuStrings
                  << &mViewMenuStrings;
  for (int i = 0; i < menus.size(); i++) {
    QMenu* currentMenu = menus[i];
    QMenu* moreCommands = nullptr;
    bool moreCommandsUsed = false;
    QList<QAction*> list = currentMenu->actions();
    moreCommands = list.last()->menu();
    if (moreCommands &&
        moreCommands->title().compare(tr("More Commands")) == 0) {
      for (auto& j : moreCommands->actions()) moreCommands->removeAction(j);
      QAction* separatorMoreCommands = list.at(list.length() - 2);
      currentMenu->removeAction(separatorMoreCommands);  // Separator
      delete separatorMoreCommands;
    } else {
      moreCommands = new QMenu(tr("More Commands"), currentMenu);
    }
    for (auto& j : list) currentMenu->removeAction(j);
    for (int j = 0; j < menuTextStrings.at(i)->size() - 1; j++) {
      QAction* a = menuTextStrings.at(i)->at(j + 1);
      if (Config()->getBool(
              "Gui",
              QString("Menu%1Hidden%2").arg(menuNativeNames[i]).arg(j))) {
        moreCommands->addAction(a);
        moreCommandsUsed = true;
      } else {
        currentMenu->addAction(a);
      }
    }
    if (moreCommandsUsed) {
      currentMenu->addSeparator();
      currentMenu->addMenu(moreCommands);
    } else
      delete moreCommands;
  }
}

void MainWindow::on_actionRestartAdmin_triggered() {
  DbgCmdExec("restartadmin");
}

void MainWindow::on_actionPlugins_triggered() {
  QDesktopServices::openUrl(QUrl("http://plugins.yunyoo.cn"));
}

void MainWindow::on_actionCheckUpdates_triggered() {
  mUpdateChecker->checkForUpdates();
}

void MainWindow::on_actionDefaultTheme_triggered() {
  // Delete [Theme] Selected
  BridgeSettingSet("Theme", "Selected", nullptr);
  // Load style
  loadSelectedStyle();
  // Reset [Colors] to default
  Config()->Colors = Config()->defaultColors;
  Config()->writeColors();
  // Reset [Fonts] to default
  // Config()->Fonts = Config()->defaultFonts;
  // Config()->writeFonts();
  // Remove custom colors
  BridgeSettingSet("Colors", "CustomColorCount", nullptr);
}

void MainWindow::displayPatches() { mPatchDialog->show(); }

CPUWidget *MainWindow::cpuWidget() { return mCpuWidget; }
